this repo has no description
1package main 2 3import ( 4 "context" 5 "flag" 6 "fmt" 7 "log" 8 "os" 9 "os/exec" 10 "path" 11 "path/filepath" 12 "strings" 13 "time" 14 15 "github.com/sotangled/tangled/appview/auth" 16) 17 18var ( 19 logger *log.Logger 20 logFile *os.File 21 clientIP string 22 23 // Command line flags 24 allowedUser = flag.String("user", "", "Allowed git user") 25 baseDirFlag = flag.String("base-dir", "/home/git", "Base directory for git repositories") 26 logPathFlag = flag.String("log-path", "/var/log/git-wrapper.log", "Path to log file") 27) 28 29func main() { 30 flag.Parse() 31 32 defer cleanup() 33 initLogger() 34 35 // Get client IP from SSH environment 36 if connInfo := os.Getenv("SSH_CONNECTION"); connInfo != "" { 37 parts := strings.Fields(connInfo) 38 if len(parts) > 0 { 39 clientIP = parts[0] 40 } 41 } 42 43 if *allowedUser == "" { 44 exitWithLog("access denied: no user specified") 45 } 46 47 sshCommand := os.Getenv("SSH_ORIGINAL_COMMAND") 48 49 logEvent("Connection attempt", map[string]interface{}{ 50 "user": *allowedUser, 51 "command": sshCommand, 52 "client": clientIP, 53 }) 54 55 if sshCommand == "" { 56 exitWithLog("access denied: we don't serve interactive shells :)") 57 } 58 59 cmdParts := strings.Fields(sshCommand) 60 if len(cmdParts) < 2 { 61 exitWithLog("invalid command format") 62 } 63 64 gitCommand := cmdParts[0] 65 66 // example.com/repo 67 handlePath := strings.Trim(cmdParts[1], "'") 68 repoName := handleToDID(handlePath) 69 70 validCommands := map[string]bool{ 71 "git-receive-pack": true, 72 "git-upload-pack": true, 73 "git-upload-archive": true, 74 } 75 if !validCommands[gitCommand] { 76 exitWithLog("access denied: invalid git command") 77 } 78 79 did := path.Dir(repoName) 80 if gitCommand != "git-upload-pack" { 81 if !isAllowedUser(*allowedUser, did) { 82 exitWithLog("access denied: user not allowed") 83 } 84 } 85 86 fullPath := filepath.Join(*baseDirFlag, repoName) 87 fullPath = filepath.Clean(fullPath) 88 89 logEvent("Processing command", map[string]interface{}{ 90 "user": *allowedUser, 91 "command": gitCommand, 92 "repo": repoName, 93 "fullPath": fullPath, 94 "client": clientIP, 95 }) 96 97 cmd := exec.Command(gitCommand, fullPath) 98 cmd.Stdout = os.Stdout 99 cmd.Stderr = os.Stderr 100 cmd.Stdin = os.Stdin 101 102 if err := cmd.Run(); err != nil { 103 exitWithLog(fmt.Sprintf("command failed: %v", err)) 104 } 105 106 logEvent("Command completed", map[string]interface{}{ 107 "user": *allowedUser, 108 "command": gitCommand, 109 "repo": repoName, 110 "success": true, 111 }) 112} 113 114func handleToDID(handlePath string) string { 115 handle := path.Dir(handlePath) 116 117 ident, err := auth.ResolveIdent(context.Background(), handle) 118 if err != nil { 119 exitWithLog(fmt.Sprintf("error resolving handle: %v", err)) 120 } 121 122 // did:plc:foobarbaz/repo 123 didPath := filepath.Join(ident.DID.String(), path.Base(handlePath)) 124 125 return didPath 126} 127 128func initLogger() { 129 var err error 130 logFile, err = os.OpenFile(*logPathFlag, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) 131 if err != nil { 132 fmt.Fprintf(os.Stderr, "failed to open log file: %v\n", err) 133 os.Exit(1) 134 } 135 136 logger = log.New(logFile, "", 0) 137} 138 139func logEvent(event string, fields map[string]interface{}) { 140 entry := fmt.Sprintf( 141 "timestamp=%q event=%q", 142 time.Now().Format(time.RFC3339), 143 event, 144 ) 145 146 for k, v := range fields { 147 entry += fmt.Sprintf(" %s=%q", k, v) 148 } 149 150 logger.Println(entry) 151} 152 153func exitWithLog(message string) { 154 logEvent("Access denied", map[string]interface{}{ 155 "error": message, 156 }) 157 logFile.Sync() 158 fmt.Fprintf(os.Stderr, "error: %s\n", message) 159 os.Exit(1) 160} 161 162func cleanup() { 163 if logFile != nil { 164 logFile.Sync() 165 logFile.Close() 166 } 167} 168 169func isAllowedUser(user, did string) bool { 170 return user == did 171}