1
This commit is contained in:
+41
-41
@@ -1,13 +1,13 @@
|
||||
// SSH-transport (public key) access checks.
|
||||
//
|
||||
// An SSH key is matched at request time by fingerprint. AccessBySSHKey resolves
|
||||
// a presented key to an SSHKey row and checks it against the repo's (or its
|
||||
// namespace's) ACL -- the state-layer half of the IState.AccessBySSHKey
|
||||
// contract. SSH has no anonymous path: the key must be registered, else
|
||||
// ErrDenied.
|
||||
//
|
||||
// SSH keys are created via ACLUpsertSSHKeyOnNS/OnRepo (IStateMut); there is no
|
||||
// separate AddSSHKey and no list/management read surface.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
package state
|
||||
|
||||
@@ -22,17 +22,17 @@ import (
|
||||
"simplegit/common"
|
||||
)
|
||||
|
||||
// AccessBySSHKey authorizes key for perm on owner/name. SSH has no anonymous
|
||||
// path: the key must be registered, else ErrDenied. A registered key may read a
|
||||
// public repo without a grant; anything else needs an ACL grant (on the repo or
|
||||
// its namespace) whose rank satisfies perm. Returns (true, nil) on allow;
|
||||
// (false, ErrDenied) on an unknown key, unknown repo, or insufficient grant.
|
||||
// skip-auth allows all.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func (s *LocalState) AccessBySSHKey(ctx context.Context, key ssh.PublicKey, owner, name string, perm common.Perm) (bool, error) {
|
||||
r, err := s.findRepo(owner, name)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrRepoNotFound) {
|
||||
return false, ErrDenied // never leak existence
|
||||
return false, ErrDenied
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
@@ -43,15 +43,15 @@ func (s *LocalState) AccessBySSHKey(ctx context.Context, key ssh.PublicKey, owne
|
||||
return false, fmt.Errorf("lookup ssh key: %w", err)
|
||||
}
|
||||
if !has {
|
||||
return false, ErrDenied // unknown key
|
||||
return false, ErrDenied
|
||||
}
|
||||
// best-effort last-used stamp; must not block access
|
||||
|
||||
now := time.Now()
|
||||
if _, err := s.engine.ID(sk.ID).Cols("last_used_at").Update(&SSHKey{LastUsedAt: &now}); err == nil {
|
||||
sk.LastUsedAt = &now
|
||||
}
|
||||
|
||||
// public read is open to any registered key
|
||||
|
||||
if perm == common.PermRead && !r.IsPrivate {
|
||||
return true, nil
|
||||
}
|
||||
@@ -65,21 +65,21 @@ func (s *LocalState) AccessBySSHKey(ctx context.Context, key ssh.PublicKey, owne
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// KnownSSHKey reports whether key is an admissible SSH credential: it must be
|
||||
// registered AND carry at least one ACL grant. A registered key with no grant
|
||||
// (an orphan -- e.g. its grant was ACLDelete'd, or its repo was DeleteRepo'd)
|
||||
// is rejected here, at the PublicKeyCallback, so the SSH client falls through
|
||||
// to another offered key that does have a grant.
|
||||
//
|
||||
// This mirrors Gitea's model where every registered key belongs to a user
|
||||
// (principal): a key without a principal can never authorize anything, so it
|
||||
// is not admitted. simplegit's namespace plays the principal role -- keys
|
||||
// granted on a namespace are all equivalent for that namespace's repos, so
|
||||
// which one the client offers first no longer matters (the multi-key footgun).
|
||||
// SSH access therefore requires a grant; public repos stay anonymously
|
||||
// readable over HTTP.
|
||||
//
|
||||
// skip-auth admits everything (no engine).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func (s *LocalState) KnownSSHKey(key ssh.PublicKey) (bool, error) {
|
||||
var sk SSHKey
|
||||
has, err := s.engine.Where("fingerprint = ?", fingerprint(key)).Get(&sk)
|
||||
@@ -87,11 +87,11 @@ func (s *LocalState) KnownSSHKey(key ssh.PublicKey) (bool, error) {
|
||||
return false, fmt.Errorf("lookup ssh key: %w", err)
|
||||
}
|
||||
if !has {
|
||||
return false, ErrDenied // unknown key
|
||||
return false, ErrDenied
|
||||
}
|
||||
// Admit only keys with at least one grant. A grant-less registered key can
|
||||
// never pass AccessBySSHKey, and admitting it would block the client from
|
||||
// trying a granted key.
|
||||
|
||||
|
||||
|
||||
hasGrant, err := s.keyHasGrant(sk.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -102,8 +102,8 @@ func (s *LocalState) KnownSSHKey(key ssh.PublicKey) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// keyHasGrant reports whether SSH key credID has any ACL grant (repo or
|
||||
// namespace), regardless of perm.
|
||||
|
||||
|
||||
func (s *LocalState) keyHasGrant(credID int64) (bool, error) {
|
||||
count, err := s.engine.Where("cred_type = ? AND cred_id = ?", CredTypeSSH, credID).Count(&ACL{})
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user