cmd/gitbayd/system.go
113 lines · 3374 bytes
1package main
2
3import (
4 "fmt"
5 "os"
6 "time"
7
8 "github.com/spf13/cobra"
9 "golang.org/x/crypto/ssh"
10
11 "gitbay.org/gitbay/internal/config"
12 "gitbay.org/gitbay/internal/control"
13 "gitbay.org/gitbay/internal/protocol"
14 "gitbay.org/gitbay/internal/sshd"
15)
16
17// authorizedKeysCmd backs sshd's AuthorizedKeysCommand in system mode:
18//
19// AuthorizedKeysCommand /usr/local/bin/gitbayd --config /etc/gitbay/config.toml authorized-keys %t %k
20// AuthorizedKeysCommandUser gitbay
21//
22// It prints a forced-command authorized_keys line for registered keys and
23// nothing for unknown ones — so unknown keys fail authentication inside
24// sshd, before any forge code runs. That is why system mode requires
25// registration = "closed".
26func authorizedKeysCmd() *cobra.Command {
27 return &cobra.Command{
28 Use: "authorized-keys <key-type> <key-base64>",
29 Hidden: true,
30 Args: cobra.ExactArgs(2),
31 RunE: func(cmd *cobra.Command, args []string) error {
32 cfg, err := config.Load(configPath)
33 if err != nil {
34 return err
35 }
36 st, err := openStore(cfg)
37 if err != nil {
38 return err
39 }
40 defer st.Close()
41
42 pub, _, _, _, err := ssh.ParseAuthorizedKey([]byte(args[0] + " " + args[1]))
43 if err != nil {
44 return nil // unparseable key: no output, auth fails
45 }
46 key, err := st.SSHKeyByFingerprint(ssh.FingerprintSHA256(pub))
47 if err != nil || key.Expired(time.Now()) {
48 return nil // unknown or expired key: no output, auth fails
49 }
50 self, err := os.Executable()
51 if err != nil {
52 return err
53 }
54 fmt.Printf("restrict,command=\"%s --config %s shell --key-id %d\" %s %s\n",
55 self, configPath, key.ID, args[0], args[1])
56 return nil
57 },
58 }
59}
60
61// shellCmd is the forced command sshd runs for an authenticated key. The
62// original client command arrives in SSH_ORIGINAL_COMMAND; dispatch is the
63// same code path as the embedded listener.
64func shellCmd() *cobra.Command {
65 var keyID int64
66 cmd := &cobra.Command{
67 Use: "shell",
68 Hidden: true,
69 Args: cobra.NoArgs,
70 RunE: func(cmd *cobra.Command, args []string) error {
71 cfg, err := config.Load(configPath)
72 if err != nil {
73 return err
74 }
75 st, err := openStore(cfg)
76 if err != nil {
77 return err
78 }
79 defer st.Close()
80
81 key, err := st.SSHKeyByID(keyID)
82 if err != nil {
83 fmt.Fprintln(os.Stderr, "key no longer registered")
84 os.Exit(protocol.ExitDenied)
85 }
86 if key.Expired(time.Now()) {
87 fmt.Fprintln(os.Stderr, "this key has expired; remove it and add a new one")
88 os.Exit(protocol.ExitDenied)
89 }
90 user, err := st.UserByID(key.UserID)
91 if err != nil {
92 fmt.Fprintln(os.Stderr, "account no longer exists")
93 os.Exit(protocol.ExitDenied)
94 }
95 _ = st.TouchSSHKey(key.ID)
96
97 cmdline := os.Getenv("SSH_ORIGINAL_COMMAND")
98 if cmdline == "" {
99 fmt.Fprintf(os.Stderr, "gitbay control plane: interactive shells are not available.\nTry: ssh <host> help\n")
100 os.Exit(protocol.ExitUsage)
101 }
102 // Each forced command is its own process, so there is no
103 // shared pack or push budget in system mode.
104 code := sshd.Exec(cfg, st, nil, nil, user, key, control.ParseTerm(os.Getenv("GITBAY_TERM")), cmdline, os.Stdin, os.Stdout, os.Stderr, nil, nil, nil)
105 st.Close()
106 os.Exit(code)
107 return nil
108 },
109 }
110 cmd.Flags().Int64Var(&keyID, "key-id", 0, "registered key id (set by authorized-keys)")
111 cmd.MarkFlagRequired("key-id")
112 return cmd
113}