AUTH-1943: Adds session logging
This commit is contained in:
parent
f41121f4e5
commit
5e85a8bd16
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/cloudflare/cloudflared/metrics"
|
||||
"github.com/cloudflare/cloudflared/origin"
|
||||
"github.com/cloudflare/cloudflared/signal"
|
||||
"github.com/cloudflare/cloudflared/sshlog"
|
||||
"github.com/cloudflare/cloudflared/sshserver"
|
||||
"github.com/cloudflare/cloudflared/supervisor"
|
||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||
|
@ -383,8 +384,9 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
|||
uploadManager.Start()
|
||||
}
|
||||
|
||||
logManager := sshlog.New()
|
||||
sshServerAddress := "127.0.0.1:" + c.String(sshPortFlag)
|
||||
server, err := sshserver.New(logger, sshServerAddress, shutdownC, c.Duration(sshIdleTimeoutFlag), c.Duration(sshMaxTimeoutFlag))
|
||||
server, err := sshserver.New(logManager, logger, sshServerAddress, shutdownC, c.Duration(sshIdleTimeoutFlag), c.Duration(sshMaxTimeoutFlag))
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Cannot create new SSH Server")
|
||||
return errors.Wrap(err, "Cannot create new SSH Server")
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
package sshlog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
logTimeFormat = "2006-01-02T15-04-05.000"
|
||||
megabyte = 1024 * 1024
|
||||
)
|
||||
|
||||
// Logger will buffer and write events to disk
|
||||
type Logger struct {
|
||||
sync.Mutex
|
||||
filename string
|
||||
file *os.File
|
||||
writeBuffer *bufio.Writer
|
||||
logger *logrus.Logger
|
||||
done chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewLogger creates a Logger instance. A buffer is created that needs to be
|
||||
// drained and closed when the caller is finished, so instances should call
|
||||
// Close when finished with this Logger instance. Writes will be flushed to disk
|
||||
// every second (fsync). filename is the name of the logfile to be created. The
|
||||
// logger variable is a logrus that will log all i/o, filesystem error etc, that
|
||||
// that shouldn't end execution of the logger, but are useful to report to the
|
||||
// caller.
|
||||
func NewLogger(filename string, logger *logrus.Logger) (*Logger, error) {
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := &Logger{filename: filename,
|
||||
file: f,
|
||||
writeBuffer: bufio.NewWriter(f),
|
||||
logger: logger,
|
||||
done: make(chan struct{})}
|
||||
go l.writer()
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Writes to a log buffer. Implements the io.Writer interface.
|
||||
func (l *Logger) Write(p []byte) (n int, err error) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
return l.writeBuffer.Write(p)
|
||||
}
|
||||
|
||||
// Close drains anything left in the buffer and cleans up any resources still
|
||||
// in use.
|
||||
func (l *Logger) Close() error {
|
||||
l.once.Do(func() {
|
||||
close(l.done)
|
||||
})
|
||||
if err := l.write(); err != nil {
|
||||
return err
|
||||
}
|
||||
return l.file.Close()
|
||||
}
|
||||
|
||||
// writer is the run loop that handles draining the write buffer and syncing
|
||||
// data to disk.
|
||||
func (l *Logger) writer() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := l.write(); err != nil {
|
||||
l.logger.Errorln(err)
|
||||
}
|
||||
case <-l.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write does the actual system write calls to disk and does a rotation if the
|
||||
// file size limit has been reached. Since the rotation happens at the end,
|
||||
// the rotation is a soft limit (aka the file can be bigger than the max limit
|
||||
// because of the final buffer flush)
|
||||
func (l *Logger) write() error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if l.writeBuffer.Buffered() <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := l.writeBuffer.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := l.file.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if l.shouldRotate() {
|
||||
return l.rotate()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// shouldRotate checks to see if the current file should be rotated to a new
|
||||
// logfile.
|
||||
func (l *Logger) shouldRotate() bool {
|
||||
info, err := l.file.Stat()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return info.Size() >= 100*megabyte
|
||||
}
|
||||
|
||||
// rotate creates a new logfile with the existing filename and renames the
|
||||
// existing file with a current timestamp.
|
||||
func (l *Logger) rotate() error {
|
||||
if err := l.file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// move the existing file
|
||||
newname := rotationName(l.filename)
|
||||
if err := os.Rename(l.filename, newname); err != nil {
|
||||
return fmt.Errorf("can't rename log file: %s", err)
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(l.filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open new logfile %s", err)
|
||||
}
|
||||
l.file = f
|
||||
l.writeBuffer = bufio.NewWriter(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
// rotationName creates a new filename from the given name, inserting a timestamp
|
||||
// between the filename and the extension.
|
||||
func rotationName(name string) string {
|
||||
dir := filepath.Dir(name)
|
||||
filename := filepath.Base(name)
|
||||
ext := filepath.Ext(filename)
|
||||
prefix := filename[:len(filename)-len(ext)]
|
||||
t := time.Now()
|
||||
timestamp := t.Format(logTimeFormat)
|
||||
return filepath.Join(dir, fmt.Sprintf("%s-%s%s", prefix, timestamp, ext))
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package sshlog
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Manager be managing logs bruh
|
||||
type Manager interface {
|
||||
NewLogger(string, *logrus.Logger) (io.WriteCloser, error)
|
||||
}
|
||||
|
||||
type manager struct{}
|
||||
|
||||
// New creates a new instance of a log manager
|
||||
func New() Manager {
|
||||
return &manager{}
|
||||
}
|
||||
|
||||
func (m *manager) NewLogger(name string, logger *logrus.Logger) (io.WriteCloser, error) {
|
||||
return NewLogger(name, logger)
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
package sshserver
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -15,8 +14,11 @@ import (
|
|||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cloudflare/cloudflared/sshlog"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/gliderlabs/ssh"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -26,9 +28,10 @@ type SSHServer struct {
|
|||
shutdownC chan struct{}
|
||||
caCert ssh.PublicKey
|
||||
getUserFunc func(string) (*User, error)
|
||||
logManager sshlog.Manager
|
||||
}
|
||||
|
||||
func New(logger *logrus.Logger, address string, shutdownC chan struct{}, idleTimeout, maxTimeout time.Duration) (*SSHServer, error) {
|
||||
func New(logManager sshlog.Manager, logger *logrus.Logger, address string, shutdownC chan struct{}, idleTimeout, maxTimeout time.Duration) (*SSHServer, error) {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -42,6 +45,7 @@ func New(logger *logrus.Logger, address string, shutdownC chan struct{}, idleTim
|
|||
logger: logger,
|
||||
shutdownC: shutdownC,
|
||||
getUserFunc: lookupUser,
|
||||
logManager: logManager,
|
||||
}
|
||||
|
||||
if err := sshServer.configureHostKeys(); err != nil {
|
||||
|
@ -67,6 +71,14 @@ func (s *SSHServer) Start() error {
|
|||
}
|
||||
|
||||
func (s *SSHServer) connectionHandler(session ssh.Session) {
|
||||
sessionID, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
if _, err := io.WriteString(session, "Failed to generate session ID\n"); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to generate session ID: Failed to write to SSH session")
|
||||
}
|
||||
s.CloseSession(session)
|
||||
}
|
||||
|
||||
// Get uid and gid of user attempting to login
|
||||
sshUser, ok := session.Context().Value("sshUser").(*User)
|
||||
if !ok || sshUser == nil {
|
||||
|
@ -134,11 +146,16 @@ func (s *SSHServer) connectionHandler(session ssh.Session) {
|
|||
defer pr.Close()
|
||||
defer pw.Close()
|
||||
|
||||
scanner := bufio.NewScanner(pr)
|
||||
go func() {
|
||||
for scanner.Scan() {
|
||||
s.logger.Info(scanner.Text())
|
||||
logger, err := s.logManager.NewLogger(fmt.Sprintf("%s-session.log", sessionID), s.logger)
|
||||
if err != nil {
|
||||
if _, err := io.WriteString(session, "Failed to create log\n"); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create log: Failed to write to SSH session")
|
||||
}
|
||||
s.CloseSession(session)
|
||||
}
|
||||
defer logger.Close()
|
||||
go func() {
|
||||
io.Copy(logger, pr)
|
||||
}()
|
||||
|
||||
// Write outgoing command output to both the command recorder, and remote user
|
||||
|
|
|
@ -5,13 +5,15 @@ package sshserver
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/sshlog"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type SSHServer struct{}
|
||||
|
||||
func New(_ *logrus.Logger, _ string, _ chan struct{}, _, _ time.Duration) (*SSHServer, error) {
|
||||
func New(_ sshlog.Manager, _ *logrus.Logger, _ string, _ chan struct{}, _, _ time.Duration) (*SSHServer, error) {
|
||||
return nil, errors.New("cloudflared ssh server is not supported on windows")
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue