Use authorized keys file to gate git write access

Toby Padilla created

Change summary

main.go                      |  9 ++++---
server/middleware/git/git.go | 43 ++++++++++++++++++++++++++++++++++++-
2 files changed, 46 insertions(+), 6 deletions(-)

Detailed changes

main.go 🔗

@@ -13,9 +13,10 @@ import (
 )
 
 type Config struct {
-	Port     int    `env:"SMOOTHIE_PORT" default:"23231"`
-	KeyPath  string `env:"SMOOTHIE_KEY_PATH" default:".ssh/smoothie_server_ed25519"`
-	RepoPath string `env:"SMOOTHIE_REPO_PATH" default:".repos"`
+	Port         int    `env:"SMOOTHIE_PORT" default:"23231"`
+	KeyPath      string `env:"SMOOTHIE_KEY_PATH" default:".ssh/smoothie_server_ed25519"`
+	RepoAuthPath string `env:"SMOOTHIE_KEY_PATH" default:".ssh/smoothie_git_authorized_keys"`
+	RepoPath     string `env:"SMOOTHIE_REPO_PATH" default:".repos"`
 }
 
 func main() {
@@ -28,7 +29,7 @@ func main() {
 		cfg.Port,
 		cfg.KeyPath,
 		bm.Middleware(tui.SessionHandler, tea.WithAltScreen()),
-		gm.Middleware(cfg.RepoPath),
+		gm.Middleware(cfg.RepoPath, cfg.RepoAuthPath),
 		lm.Middleware(),
 	)
 	if err != nil {

server/middleware/git/git.go 🔗

@@ -1,8 +1,10 @@
 package git
 
 import (
+	"bufio"
 	"context"
 	"fmt"
+	"log"
 	"os"
 	"os/exec"
 	"smoothie/server/middleware"
@@ -10,13 +12,50 @@ import (
 	"github.com/gliderlabs/ssh"
 )
 
-func Middleware(repoDir string) middleware.Middleware {
+func Middleware(repoDir string, authorizedKeysPath string) middleware.Middleware {
+	authedKeys := make([]ssh.PublicKey, 0)
+	hasAuth, err := fileExists(authorizedKeysPath)
+	if err != nil {
+		log.Fatal(err)
+	}
+	if hasAuth {
+		f, err := os.Open(authorizedKeysPath)
+		if err != nil {
+			log.Fatal(err)
+		}
+		defer f.Close()
+		scanner := bufio.NewScanner(f)
+		for scanner.Scan() {
+			pt := scanner.Text()
+			log.Printf("Adding authorized key: %s", pt)
+			pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pt))
+			if err != nil {
+				log.Fatal(err)
+			}
+			authedKeys = append(authedKeys, pk)
+		}
+		if err := scanner.Err(); err != nil {
+			log.Fatal(err)
+		}
+	}
 	return func(sh ssh.Handler) ssh.Handler {
 		return func(s ssh.Session) {
 			cmd := s.Command()
 			if len(cmd) == 2 {
 				switch cmd[0] {
-				case "git-upload-pack", "git-receive-pack", "git-upload-archive":
+				case "git-upload-pack", "git-upload-archive", "git-receive-pack":
+					if hasAuth && cmd[0] == "git-receive-pack" {
+						authed := false
+						for _, pk := range authedKeys {
+							if ssh.KeysEqual(pk, s.PublicKey()) {
+								authed = true
+							}
+						}
+						if !authed {
+							fatalGit(s, fmt.Errorf("you are not authorized to do this"))
+							break
+						}
+					}
 					r := cmd[1]
 					rp := fmt.Sprintf("%s%s", repoDir, r)
 					ctx := s.Context()