repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

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
M pkg/apps/auth/api.go
+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 	}
M pkg/apps/auth/api_test.go
+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)
M pkg/shared/ssh.go
+19, -14
 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)