Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

at master 3.6 kB View raw
1package keyfetch 2 3import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "os" 10 "strings" 11 12 "github.com/urfave/cli/v3" 13 "golang.org/x/crypto/ssh" 14 "tangled.org/core/log" 15) 16 17func Command() *cli.Command { 18 return &cli.Command{ 19 Name: "keys", 20 Usage: "fetch public keys from the knot server", 21 Action: Run, 22 Flags: []cli.Flag{ 23 &cli.StringFlag{ 24 Name: "output", 25 Aliases: []string{"o"}, 26 Usage: "output format (table, json, authorized-keys)", 27 Value: "table", 28 }, 29 &cli.StringFlag{ 30 Name: "internal-api", 31 Usage: "internal API endpoint", 32 Value: "http://localhost:5444", 33 }, 34 &cli.StringFlag{ 35 Name: "git-dir", 36 Usage: "base directory for git repos", 37 Value: "/home/git", 38 }, 39 &cli.StringFlag{ 40 Name: "log-path", 41 Usage: "path to log file", 42 Value: "/home/git/log", 43 }, 44 &cli.StringFlag{ 45 Name: "guard-path", 46 Usage: "path to the knot binary for the authorized_keys forced command (defaults to os.Executable)", 47 }, 48 &cli.BoolFlag{ 49 Name: "secure-mode", 50 Usage: "emit -secure-mode in the authorized_keys forced command", 51 }, 52 }, 53 } 54} 55 56func Run(ctx context.Context, cmd *cli.Command) error { 57 l := log.FromContext(ctx) 58 59 internalApi := cmd.String("internal-api") 60 gitDir := cmd.String("git-dir") 61 logPath := cmd.String("log-path") 62 output := cmd.String("output") 63 64 executablePath := cmd.String("guard-path") 65 if executablePath == "" { 66 var err error 67 executablePath, err = os.Executable() 68 if err != nil { 69 l.Error("error getting path of executable", "error", err) 70 return err 71 } 72 } 73 74 resp, err := http.Get(internalApi + "/keys") 75 if err != nil { 76 l.Error("error reaching internal API endpoint; is the knot server running?", "error", err) 77 return err 78 } 79 defer resp.Body.Close() 80 81 body, err := io.ReadAll(resp.Body) 82 if err != nil { 83 l.Error("error reading response body", "error", err) 84 return err 85 } 86 87 var data []map[string]any 88 err = json.Unmarshal(body, &data) 89 if err != nil { 90 l.Error("error unmarshalling response body", "error", err) 91 return err 92 } 93 94 switch output { 95 case "json": 96 prettyJSON, err := json.MarshalIndent(data, "", " ") 97 if err != nil { 98 l.Error("error pretty printing JSON", "error", err) 99 return err 100 } 101 102 if _, err := os.Stdout.Write(prettyJSON); err != nil { 103 l.Error("error writing to stdout", "error", err) 104 return err 105 } 106 case "authorized-keys": 107 formatted := formatKeyData(executablePath, gitDir, logPath, internalApi, cmd.Bool("secure-mode"), data) 108 _, err := os.Stdout.Write([]byte(formatted)) 109 if err != nil { 110 l.Error("error writing to stdout", "error", err) 111 return err 112 } 113 case "table": 114 fmt.Printf("%-40s %-40s\n", "DID", "KEY") 115 fmt.Println(strings.Repeat("-", 80)) 116 117 for _, entry := range data { 118 did, _ := entry["did"].(string) 119 key, _ := entry["key"].(string) 120 fmt.Printf("%-40s %-40s\n", did, key) 121 } 122 } 123 return nil 124} 125 126func formatKeyData(executablePath, gitDir, logPath, endpoint string, secureMode bool, data []map[string]any) string { 127 secureFlag := "" 128 if secureMode { 129 secureFlag = " -secure-mode" 130 } 131 var result string 132 for _, entry := range data { 133 raw, _ := entry["key"].(string) 134 key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(raw)) 135 if err != nil { 136 continue 137 } 138 result += fmt.Sprintf( 139 `command="%s guard -git-dir %s -user %s -log-path %s -internal-api %s%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s`+"\n", 140 executablePath, gitDir, entry["did"], logPath, endpoint, secureFlag, ssh.MarshalAuthorizedKey(key)) 141 } 142 return result 143}