cmd/gitbayd/system.go

v1.43.0
gitbay/cmd/gitbayd/system.go history · blame · raw

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}