package main import ( "crypto" "crypto/rand" "crypto/rsa" "errors" "flag" "fmt" "io" "net" "os" "os/signal" "strings" "syscall" "github.com/sirupsen/logrus" "github.com/stoggi/sshrimp/internal/config" "github.com/stoggi/sshrimp/internal/signer" "github.com/stoggi/sshrimp/internal/sshrimpagent" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) var sigs = []os.Signal{os.Kill, os.Interrupt} var logger = logrus.New() var log *logrus.Entry var cli struct { Config string `kong:"arg,type='string',help='sshrimp config file (default: ${config_file} or ${env_var_name} environment variable)',default='${config_file}',env='SSHRIMP_CONFIG'"` } func main() { flag.StringVar(&cli.Config, "config", config.DefaultPath, "sshrimp config file") v := flag.Bool("v", false, "enable verbose logging") logger.SetFormatter(&logrus.TextFormatter{ FullTimestamp: true, }) log = logger.WithFields(logrus.Fields{ "pid": os.Getpid(), }) sshrimpagent.Log = log signer.Log = log flag.Parse() if *v { logger.SetLevel(logrus.TraceLevel) } c := config.NewSSHrimpWithDefaults() err := c.Read(cli.Config) if err != nil { panic(err) } err = launchAgent(c) if err != nil { panic(err) } } func launchAgent(c *config.SSHrimp) error { var ( err error listener net.Listener privateKey crypto.Signer signer ssh.Signer logMessage string ) log.Traceln("Creating socket") if _, err = os.Stat(c.Agent.Socket); err == nil { log.Tracef("File already exists at %s", c.Agent.Socket) conn, sockErr := net.Dial("unix", c.Agent.Socket) if conn == nil { logMessage = "conn is nil" } if sockErr == nil { // socket is accepting connections logMessage += "err reports successful connection" conn.Close() log.Errorf("Socket connected successfully %s", logMessage) return fmt.Errorf("socket %s already exists", c.Agent.Socket) } log.Tracef("Socket is not connected %s", logMessage) if os.Remove(c.Agent.Socket) == nil { // socket is not accepting connections, assuming safe to remove log.Traceln("Deleting socket: success") } else { log.Errorf("Deleting socket: fail") } } // This affects all files created for the process. Since this is a sensitive // socket, only allow the current user to write to the socket. syscall.Umask(0077) listener, err = net.Listen("unix", c.Agent.Socket) if err != nil { return err } defer listener.Close() fmt.Printf("listening on %s\n", c.Agent.Socket) // Generate a new SSH private/public key pair log.Tracef("Generating RSA %d ssh keys", 2048) privateKey, err = rsa.GenerateKey(rand.Reader, 2048) if err != nil { return err } log.Traceln("Creating new signer from key") signer, err = ssh.NewSignerFromKey(privateKey) if err != nil { return err } // Create the sshrimp agent with our configuration and the private key signer log.Traceln("Creating new sshrimp agent from signer and config") sshrimpAgent := sshrimpagent.NewSSHrimpAgent(c, signer) // Listen for signals so that we can close the listener and exit nicely log.Debugf("Exiting on signals: %v", sigs) osSignals := make(chan os.Signal) signal.Notify(osSignals, sigs...) go func() { <-osSignals listener.Close() }() log.Traceln("Starting main loop") // Accept connections and serve the agent for { var conn net.Conn conn, err = listener.Accept() if err != nil { log.Errorf("Error accepting connection: %v", err) if strings.Contains(err.Error(), "use of closed network connection") { // Occurs if the user interrupts the agent with a ctrl-c signal return nil } return err } log.Traceln("Serving agent") if err = agent.ServeAgent(sshrimpAgent, conn); err != nil && !errors.Is(err, io.EOF) { log.Errorf("Error serving agent: %v", err) return err } } }