- commit
- 59aba61
- parent
- bb8ef88
- author
- Eric Bower
- date
- 2025-12-13 20:49:35 -0500 EST
feat(auth): authenticate key handler using ssh cert
3 files changed,
+42,
-20
+22,
-5
1@@ -23,6 +23,7 @@ import (
2 "github.com/picosh/utils/pipe"
3 "github.com/picosh/utils/pipe/metrics"
4 "github.com/prometheus/client_golang/prometheus/promhttp"
5+ "golang.org/x/crypto/ssh"
6 )
7
8 //go:embed html/* public/*
9@@ -245,22 +246,38 @@ func keyHandler(apiConfig *shared.ApiConfig) http.HandlerFunc {
10
11 space := r.URL.Query().Get("space")
12
13- apiConfig.Cfg.Logger.Info(
14- "handle key",
15+ log := apiConfig.Cfg.Logger.With(
16 "remoteAddress", data.RemoteAddress,
17 "user", data.Username,
18 "space", space,
19 "publicKey", data.PublicKey,
20 )
21
22- user, err := apiConfig.Dbpool.FindUserForKey(data.Username, data.PublicKey)
23+ log.Info("handle key")
24+
25+ key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(data.PublicKey))
26 if err != nil {
27- apiConfig.Cfg.Logger.Error(err.Error())
28+ log.Error("parse authorized key", "err", err)
29+ http.Error(w, err.Error(), http.StatusBadRequest)
30+ return
31+ }
32+
33+ pubkey, err := shared.PubkeyCertVerify(key, space)
34+ if err != nil {
35+ log.Error("pubkey cert verify", "err", err)
36+ http.Error(w, err.Error(), http.StatusBadRequest)
37+ return
38+ }
39+
40+ user, err := apiConfig.Dbpool.FindUserForKey(data.Username, pubkey)
41+ if err != nil {
42+ log.Error("find user for key", "err", err)
43 w.WriteHeader(http.StatusUnauthorized)
44 return
45 }
46
47 if !apiConfig.HasPlusOrSpace(user, space) {
48+ log.Error("key handler unauthorized")
49 w.WriteHeader(http.StatusUnauthorized)
50 return
51 }
52@@ -274,7 +291,7 @@ func keyHandler(apiConfig *shared.ApiConfig) http.HandlerFunc {
53 w.WriteHeader(http.StatusOK)
54 err = json.NewEncoder(w).Encode(user)
55 if err != nil {
56- apiConfig.Cfg.Logger.Error(err.Error())
57+ log.Error("json encode", "err", err)
58 http.Error(w, err.Error(), http.StatusInternalServerError)
59 }
60 }
+1,
-1
1@@ -97,7 +97,7 @@ func TestKey(t *testing.T) {
2
3 data := sishData{
4 Username: testUsername,
5- PublicKey: "zzz",
6+ PublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxVPgEqtWOa5l0QHZV6TQKhV+l46SAXU07c9RuHlGka test@pico",
7 }
8 jso, err := json.Marshal(data)
9 bail(err)
1@@ -33,41 +33,46 @@ func NewSshAuthHandler(dbh AuthFindUser, logger *slog.Logger, principal string)
2 }
3 }
4
5-func (r *SshAuthHandler) PubkeyAuthHandler(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
6- log := r.Logger
7- var user *db.User
8- var err error
9- pubkey := ""
10-
11+func PubkeyCertVerify(key ssh.PublicKey, srcPrincipal string) (string, error) {
12 cert, ok := key.(*ssh.Certificate)
13 if ok {
14 if cert.CertType != ssh.UserCert {
15- return nil, fmt.Errorf("ssh-cert has type %d", cert.CertType)
16+ return "", fmt.Errorf("ssh-cert has type %d", cert.CertType)
17 }
18
19 found := false
20 for _, princ := range cert.ValidPrincipals {
21- if princ == "admin" || princ == r.Principal {
22+ if princ == "admin" || princ == srcPrincipal {
23 found = true
24 break
25 }
26 }
27 if !found {
28- return nil, fmt.Errorf("ssh-cert principals not valid")
29+ return "", fmt.Errorf("ssh-cert principals not valid")
30 }
31
32 clock := time.Now
33 unixNow := clock().Unix()
34 if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) {
35- return nil, fmt.Errorf("ssh-cert is not yet valid")
36+ return "", fmt.Errorf("ssh-cert is not yet valid")
37 }
38 if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(ssh.CertTimeInfinity) && (unixNow >= before || before < 0) {
39- return nil, fmt.Errorf("ssh-cert has expired")
40+ return "", fmt.Errorf("ssh-cert has expired")
41 }
42
43- pubkey = utils.KeyForKeyText(cert.SignatureKey)
44- } else {
45- pubkey = utils.KeyForKeyText(key)
46+ return utils.KeyForKeyText(cert.SignatureKey), nil
47+ }
48+
49+ return utils.KeyForKeyText(key), nil
50+}
51+
52+func (r *SshAuthHandler) PubkeyAuthHandler(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
53+ log := r.Logger
54+ var user *db.User
55+ var err error
56+ pubkey, err := PubkeyCertVerify(key, r.Principal)
57+ if err != nil {
58+ return nil, err
59 }
60
61 user, err = r.DB.FindUserByPubkey(pubkey)