diff --git a/.dockerignore b/.dockerignore index caf1a8df8..232a948f3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,8 @@ web/dist release .idea coverage.out +*.rsa +*.rsa.pub # ignore any executables we build /gitness \ No newline at end of file diff --git a/.gitignore b/.gitignore index c1f1509cf..b8bc7216f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,11 +15,9 @@ release coverage.out gitness.session.sql web/cypress/node_modules +*.rsa +*.rsa.pub +node_modules/ # ignore any executables we build /gitness - -node_modules/ - -ssh/gitness.rsa -ssh/gitness.rsa.pub \ No newline at end of file diff --git a/ssh/server.go b/ssh/server.go index 173b15d71..f96205614 100644 --- a/ssh/server.go +++ b/ssh/server.go @@ -75,12 +75,8 @@ var ( "hmac-sha2-256", "hmac-sha2-512", } - defaultServerKeys = []string{"ssh/gitness.rsa"} - KeepAliveMsg = "keepalive@openssh.com" -) - -var ( - ErrHostKeysAreRequired = errors.New("host keys are required") + defaultServerKeyPath = "ssh/gitness.rsa" + KeepAliveMsg = "keepalive@openssh.com" ) type Server struct { @@ -96,7 +92,7 @@ type Server struct { KeyExchanges []string MACs []string HostKeys []string - KeepAliveInterval int + KeepAliveInterval time.Duration Verifier publickey.Service RepoCtrl *repo.Controller @@ -119,10 +115,6 @@ func (s *Server) sanitize() error { s.MACs = defaultMACs } - if len(s.HostKeys) == 0 { - s.HostKeys = defaultServerKeys - } - if s.KeepAliveInterval == 0 { s.KeepAliveInterval = 5000 } @@ -169,34 +161,22 @@ func (s *Server) ListenAndServe() error { } func (s *Server) setupHostKeys() error { - if len(s.HostKeys) == 0 { - return ErrHostKeysAreRequired - } keys := make([]string, 0, len(s.HostKeys)) - // check if file exists and append to slice keys for _, key := range s.HostKeys { _, err := os.Stat(key) if err != nil { - log.Err(err).Msgf("unable to check if %s exists", key) - continue + return fmt.Errorf("failed to read provided host key %q: %w", key, err) } keys = append(keys, key) } - // if there is no keys found then create one from HostKeys field if len(keys) == 0 { - fullpath := s.HostKeys[0] - filePath := filepath.Dir(fullpath) - - if err := os.MkdirAll(filePath, os.ModePerm); err != nil { - return fmt.Errorf("failed to create dir %s: %w", filePath, err) - } - - err := GenerateKeyPair(fullpath) + log.Debug().Msg("no host key provided - setup default key if it doesn't exist yet") + err := createKeyIfNotExists(defaultServerKeyPath) if err != nil { - return fmt.Errorf("failed to generate private key: %w", err) + return fmt.Errorf("failed to setup default key %q: %w", defaultServerKeyPath, err) } - keys = append(keys, fullpath) + keys = append(keys, defaultServerKeyPath) } // set keys to internal ssh server @@ -206,6 +186,7 @@ func (s *Server) setupHostKeys() error { log.Err(err).Msg("failed to set host key to ssh server") } } + return nil } @@ -268,7 +249,7 @@ func (s *Server) sessionHandler(session ssh.Session) { // set keep alive connection if s.KeepAliveInterval > 0 { - go s.sendKeepAliveMsg(ctx, session) + go sendKeepAliveMsg(ctx, session, s.KeepAliveInterval) } err = s.RepoCtrl.GitServicePack( @@ -302,8 +283,8 @@ func (s *Server) sessionHandler(session ssh.Session) { } } -func (s *Server) sendKeepAliveMsg(ctx context.Context, session ssh.Session) { - ticker := time.NewTicker(time.Duration(s.KeepAliveInterval)) +func sendKeepAliveMsg(ctx context.Context, session ssh.Session, interval time.Duration) { + ticker := time.NewTicker(interval) defer ticker.Stop() log.Ctx(ctx).Debug().Str("remote_addr", session.RemoteAddr().String()).Msgf("sendKeepAliveMsg") @@ -312,8 +293,11 @@ func (s *Server) sendKeepAliveMsg(ctx context.Context, session ssh.Session) { case <-ctx.Done(): return case <-ticker.C: - log.Ctx(ctx).Debug().Msg("connection: sendKeepAliveMsg: send keepalive message to a client") - _, _ = session.SendRequest(KeepAliveMsg, true, nil) + log.Ctx(ctx).Debug().Msg("send keepalive message to ssh client") + _, err := session.SendRequest(KeepAliveMsg, true, nil) + if err != nil { + log.Ctx(ctx).Debug().Err(err).Msg("failed to send keepalive message to ssh client") + } } } } @@ -332,8 +316,12 @@ func (s *Server) publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { } principal, err := s.Verifier.ValidateKey(ctx, key, enum.PublicKeyUsageAuth) + if errors.IsNotFound(err) { + log.Debug().Err(err).Msg("public key is unknown") + return false + } if err != nil { - log.Error().Err(err).Msg("failed to validate public key") + log.Warn().Err(err).Msg("failed to validate public key") return false } @@ -365,6 +353,31 @@ func sshConnectionFailed(conn net.Conn, err error) { log.Err(err).Msgf("failed connection from %s with error: %v", conn.RemoteAddr(), err) } +func createKeyIfNotExists(path string) error { + _, err := os.Stat(path) + if err == nil { + // if the path already exists there's nothing we have to do + return nil + } + if !os.IsNotExist(err) { + return fmt.Errorf("failed to check for for existence of key: %w", err) + } + + log.Debug().Msgf("generate new key at %q", path) + + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("failed to create dir %q for key: %w", dir, err) + } + + err = GenerateKeyPair(path) + if err != nil { + return fmt.Errorf("failed to generate key pair: %w", err) + } + + return nil +} + // GenerateKeyPair make a pair of public and private keys for SSH access. func GenerateKeyPair(keyPath string) error { privateKey, err := rsa.GenerateKey(rand.Reader, 4096) diff --git a/types/config.go b/types/config.go index 7230aa416..db1426e2d 100644 --- a/types/config.go +++ b/types/config.go @@ -138,7 +138,7 @@ type Config struct { } SSH struct { - Enable bool `envconfig:"GITNESS_SSH_ENABLE" default:"true"` + Enable bool `envconfig:"GITNESS_SSH_ENABLE" default:"false"` Host string `envconfig:"GITNESS_SSH_HOST"` Port int `envconfig:"GITNESS_SSH_PORT" default:"22"` // DefaultUser holds value for generating urls {user}@host:path and force check @@ -152,7 +152,7 @@ type Config struct { TrustedUserCAKeys []string `envconfig:"GITNESS_SSH_TRUSTED_USER_CA_KEYS"` TrustedUserCAKeysFile string `envconfig:"GITNESS_SSH_TRUSTED_USER_CA_KEYS_FILENAME"` TrustedUserCAKeysParsed []gossh.PublicKey - KeepAliveInterval int `envconfig:"GITNESS_SSH_KEEP_ALIVE_INTERVAL" default:"5000"` + KeepAliveInterval time.Duration `envconfig:"GITNESS_SSH_KEEP_ALIVE_INTERVAL" default:"5s"` } // CI defines configuration related to build executions.