repos / pico

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

commit
bb8ef88
parent
94c4b4d
author
Eric Bower
date
2025-12-12 20:57:46 -0500 EST
feat: access control using ssh certs
7 files changed,  +55, -16
M pkg/apps/feeds/ssh.go
+1, -1
1@@ -46,7 +46,7 @@ func StartSshServer() {
2 	}
3 	handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
4 
5-	sshAuth := shared.NewSshAuthHandler(dbh, logger)
6+	sshAuth := shared.NewSshAuthHandler(dbh, logger, "feeds")
7 
8 	// Create a new SSH server
9 	server, err := pssh.NewSSHServerWithConfig(
M pkg/apps/pastes/ssh.go
+1, -1
1@@ -45,7 +45,7 @@ func StartSshServer() {
2 		"fallback": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
3 	}
4 	handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
5-	sshAuth := shared.NewSshAuthHandler(dbh, logger)
6+	sshAuth := shared.NewSshAuthHandler(dbh, logger, "pastes")
7 
8 	// Create a new SSH server
9 	server, err := pssh.NewSSHServerWithConfig(
M pkg/apps/pgs/ssh.go
+1, -1
1@@ -34,7 +34,7 @@ func StartSshServer(cfg *PgsConfig, killCh chan error) {
2 		ctx,
3 	)
4 
5-	sshAuth := shared.NewSshAuthHandler(cfg.DB, logger)
6+	sshAuth := shared.NewSshAuthHandler(cfg.DB, logger, "pgs")
7 
8 	webTunnel := &tunkit.WebTunnelHandler{
9 		Logger:      logger,
M pkg/apps/pico/ssh.go
+3, -2
 1@@ -64,7 +64,7 @@ func StartSshServer() {
 2 		DBPool: dbpool,
 3 	}
 4 
 5-	sshAuth := shared.NewSshAuthHandler(dbpool, logger)
 6+	sshAuth := shared.NewSshAuthHandler(dbpool, logger, "pico")
 7 
 8 	// Create a new SSH server
 9 	server, err := pssh.NewSSHServerWithConfig(
10@@ -76,7 +76,8 @@ func StartSshServer() {
11 		promPort,
12 		"ssh_data/term_info_ed25519",
13 		func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
14-			perms, _ := sshAuth.PubkeyAuthHandler(conn, key)
15+			perms, err := sshAuth.PubkeyAuthHandler(conn, key)
16+			logger.Warn("pubkey auth handler", "err", err)
17 			if perms == nil {
18 				perms = &ssh.Permissions{
19 					Extensions: map[string]string{
M pkg/apps/pipe/ssh.go
+1, -1
1@@ -46,7 +46,7 @@ func StartSshServer() {
2 		Access:  syncmap.New[string, []string](),
3 	}
4 
5-	sshAuth := shared.NewSshAuthHandler(dbh, logger)
6+	sshAuth := shared.NewSshAuthHandler(dbh, logger, "pipe")
7 
8 	// Create a new SSH server
9 	server, err := pssh.NewSSHServerWithConfig(
M pkg/apps/prose/ssh.go
+1, -1
1@@ -59,7 +59,7 @@ func StartSshServer() {
2 	}
3 	handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
4 
5-	sshAuth := shared.NewSshAuthHandler(dbh, logger)
6+	sshAuth := shared.NewSshAuthHandler(dbh, logger, "prose")
7 
8 	// Create a new SSH server
9 	server, err := pssh.NewSSHServerWithConfig(
M pkg/shared/ssh.go
+47, -9
 1@@ -4,6 +4,7 @@ import (
 2 	"fmt"
 3 	"log/slog"
 4 	"strings"
 5+	"time"
 6 
 7 	"github.com/picosh/pico/pkg/db"
 8 	"github.com/picosh/utils"
 9@@ -13,8 +14,9 @@ import (
10 const adminPrefix = "admin__"
11 
12 type SshAuthHandler struct {
13-	DB     AuthFindUser
14-	Logger *slog.Logger
15+	DB        AuthFindUser
16+	Logger    *slog.Logger
17+	Principal string
18 }
19 
20 type AuthFindUser interface {
21@@ -23,18 +25,54 @@ type AuthFindUser interface {
22 	FindFeature(userID, name string) (*db.FeatureFlag, error)
23 }
24 
25-func NewSshAuthHandler(dbh AuthFindUser, logger *slog.Logger) *SshAuthHandler {
26+func NewSshAuthHandler(dbh AuthFindUser, logger *slog.Logger, principal string) *SshAuthHandler {
27 	return &SshAuthHandler{
28-		DB:     dbh,
29-		Logger: logger,
30+		DB:        dbh,
31+		Logger:    logger,
32+		Principal: principal,
33 	}
34 }
35 
36 func (r *SshAuthHandler) PubkeyAuthHandler(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
37-	pubkey := utils.KeyForKeyText(key)
38-	user, err := r.DB.FindUserByPubkey(pubkey)
39+	log := r.Logger
40+	var user *db.User
41+	var err error
42+	pubkey := ""
43+
44+	cert, ok := key.(*ssh.Certificate)
45+	if ok {
46+		if cert.CertType != ssh.UserCert {
47+			return nil, fmt.Errorf("ssh-cert has type %d", cert.CertType)
48+		}
49+
50+		found := false
51+		for _, princ := range cert.ValidPrincipals {
52+			if princ == "admin" || princ == r.Principal {
53+				found = true
54+				break
55+			}
56+		}
57+		if !found {
58+			return nil, fmt.Errorf("ssh-cert principals not valid")
59+		}
60+
61+		clock := time.Now
62+		unixNow := clock().Unix()
63+		if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) {
64+			return nil, fmt.Errorf("ssh-cert is not yet valid")
65+		}
66+		if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(ssh.CertTimeInfinity) && (unixNow >= before || before < 0) {
67+			return nil, fmt.Errorf("ssh-cert has expired")
68+		}
69+
70+		pubkey = utils.KeyForKeyText(cert.SignatureKey)
71+	} else {
72+		pubkey = utils.KeyForKeyText(key)
73+	}
74+
75+	user, err = r.DB.FindUserByPubkey(pubkey)
76 	if err != nil {
77-		r.Logger.Error(
78+		log.Error(
79 			"could not find user for key",
80 			"keyType", key.Type(),
81 			"key", string(key.Marshal()),
82@@ -44,7 +82,7 @@ func (r *SshAuthHandler) PubkeyAuthHandler(conn ssh.ConnMetadata, key ssh.Public
83 	}
84 
85 	if user.Name == "" {
86-		r.Logger.Error("username is not set")
87+		log.Error("username is not set")
88 		return nil, fmt.Errorf("username is not set")
89 	}
90