auditlog

package
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2024 License: Apache-2.0 Imports: 18 Imported by: 0

README

ContainerSSH Audit Logging Library

This is an audit logging library for ContainerSSH. Among others, it contains the encoder and decoder for the ContainerSSH Audit Log Format written in Go. This readme will guide you through the process of using this library.

⚠⚠⚠ Warning: This is a developer documentation. ⚠⚠⚠
The user documentation for ContainerSSH is located at containerssh.io.

Setting up a logging pipeline

This section will explain how to set up and use a logging pipeline. As a first step, you must create the logger. The easiest way to do that is to pass a config object. The geoIPLookupProvider is provided by the GeoIP library, while logger is a logger implementation from the Log library.

auditLogger, err := auditlog.New(cfg, geoIPLookupProvider, logger)

The cfg variable must be of the type auditlog.Config. Here's an example configuration:

config := auditlog.Config{
    Enable: true,
    Format:  "binary",
    Storage: "file",
    File: file.Config{
        Directory: "/tmp/auditlog",
    },
    Intercept: auditlog.InterceptConfig{
        Stdin:     true,
        Stdout:    true,
        Stderr:    true,
        Passwords: true,
    },
}

The logger variable must be an instance of github.com/containerssh/libcontainerssh/log/logger. The easiest way to create the logger is as follows:

logger := standard.New()

Alternatively, you can also create the audit logger using the following factory method:

auditLogger := auditlog.NewLogger(
    intercept,
    encoder,
    storage,
    logger,
)

In this case intercept is of the type InterceptConfig, encoder is an instance of codec.Encoder, storage is an instance of storage.WritableStorage, and logger is the same logger as explained above. This allows you to create a custom pipeline.

You can also trigger a shutdown of the audit logger with the Shutdown() method. This method takes a context as an argument, allowing you to specify a grace time to let the audit logger finish background processes:

auditLogger.Shutdown(
    context.WithTimeout(
        context.Background(),
        30 * time.Second,
    ),
)

Note: the logger is not guaranteed to shut down when the shutdown context expires. If there are still active connections being logged it will wait for those to finish and be written to a persistent storage before exiting. It may, however, cancel uploads to a remote storage.

Writing to the pipeline

Once the audit logging pipeline is created you can then create your first entry for a new connection:

connectionID := "0123456789ABCDEF"
connection, err := auditLogger.OnConnect(
    []byte("asdf"),
    net.TCPAddr{
        IP:   net.ParseIP("127.0.0.1"),
        Port: 2222,
        Zone: "",
    },
)

This will post a connect message to the audit log. The connection variable can then be used to send subsequent connection-specific messages:

connection.OnAuthPassword("foo", []byte("bar"))
connection.OnDisconnect()

The OnNewChannelSuccess() method also allows for the creation of a channel-specific audit logger that will log with the appropriate channel ID.

Retrieving and decoding messages

Once the messages are restored they can be retrieved by the same storage mechanism that was used to store them:

storage, err := auditlog.NewStorage(config, logger)
if err != nil {
    log.Fatalf("%v", err)
}
// This only works if the storage type is not "none"
readableStorage := storage.(storage.ReadableStorage)

The readable storage will let you list audit log entries as well as fetch individual audit logs:

logsChannel, errors := readableStorage.List()
for {
    finished := false
    select {
    case entry, ok := <-logsChannel:
        if !ok {
            finished = true
            break
        }
        // use entry.Name to reference a specific audit log
    case err, ok := <-errors:
        if !ok {
            finished = true
            break
        }
        if err != nil {
            // Handle err
        }
    }
    if finished {
        break
    }
}

Finally, you can fetch an individual audit log:

reader, err := readableStorage.OpenReader(entry.Name)
if err != nil {
    // Handle error
}

The reader is now a standard io.Reader.

Decoding messages

Messages can be decoded with the reader as follows:

// Set up the decoder
decoder := binary.NewDecoder()

// Decode messages
decodedMessageChannel, errorsChannel := decoder.Decode(reader)

for {
    finished := false
    select {
        // Fetch next message or error
        case msg, ok := <-decodedMessageChannel:
            if !ok {
                //Channel closed
                finished = true
                break
            } 
            //Handle messages
        case err := <-errorsChannel:
            if !ok {
                //Channel closed
                finished = true
                break
            } 
            // Handle error
    }
    if finished {
        break
    }
}

Tip: The <- signs are used with channels. They are used for async processing. If you are unfamiliar with them take a look at Go by Example.

Note: The Asciinema encoder doesn't have a decoder pair as the Asciinema format does not contain enough information to reconstruct the messages.

Development

In order to successfully run the tests for this library you will need a working Docker or Podman setup to run minio/minio for the S3 upload.

Manually encoding messages

If you need to encode messages by hand without a logger pipeline you can do so with an encoder implementation. This is normally not needed. We have two encoder implementations: the binary and the Asciinema encoders. You can use them like this:

geoIPLookup, err := geoip.New(...)
// Handle error 
encoder := binary.NewEncoder(logger, geoIPLookup)
// Alternatively:
// encoder := asciinema.NewEncoder(logger)

// Initialize message channel
messageChannel := make(chan message.Message)
// Initialize storage backend
storage := YourNewStorage()

go func() {
    err := encoder.Encode(messageChannel, storage)
    if err != nil {
        log.Fatalf("failed to encode messages (%w)", err)        
    }
}()

messageChannel <- message.Message{
    //Fill in message details here
}
//make sure to close the message channel so the encoder knows no more messages will come.
close(messageChannel)

Note: The encoder will run until the message channel is closed, or a disconnect message is sent.

Implementing an encoder and decoder

If you want to implement your own encoder for a custom format you can do so by implementing the Encoder interface in the codec/abstract.go file. Conversely, you can implement the Decoder interface to implement a decoder.

Implementing a writable storage

In order to provide storages you must provide an io.WriteCloser with this added function:

// Set metadata for the audit log. Can be called multiple times.
//
// startTime is the time when the connection started in unix timestamp
// sourceIp  is the IP address the user connected from
// username  is the username the user entered. The first time this method
//           is called the username will be nil, may be called subsequently
//           is the user authenticated.
SetMetadata(startTime int64, sourceIp string, username *string)

Implementing a readable storage

In order to implement a readable storage you must implement the ReadableStorage interface in storage/storage.go. You will need to implement the OpenReader() method to open a specific audit log and the List() method to list all available audit logs.

Generating the format documentation

The format documentation is autogenerated using go generate.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewEncoder

func NewEncoder(encoder config.AuditLogFormat, logger log.Logger, geoIPLookupProvider geoipprovider.LookupProvider) (codec.Encoder, error)

NewEncoder creates a new audit log encoder of the specified format.

func NewStorage

func NewStorage(cfg config.AuditLogConfig, logger log.Logger) (storage.WritableStorage, error)

NewStorage creates a new audit log storage of the specified type and with the specified configuration.

Types

type Channel

type Channel interface {
	// OnRequestUnknown creates an audit log message for a channel request that is not supported.
	OnRequestUnknown(requestID uint64, requestType string, payload []byte)
	// OnRequestDecodeFailed creates an audit log message for a channel request that is supported but could not be
	//                       decoded.
	OnRequestDecodeFailed(requestID uint64, requestType string, payload []byte, reason string)
	// OnRequestFailed is called when a backend failed to respond to a request.
	OnRequestFailed(requestID uint64, reason error)

	// OnRequestSetEnv creates an audit log message for a channel request to set an environment variable.
	OnRequestSetEnv(requestID uint64, name string, value string)
	// OnRequestExec creates an audit log message for a channel request to execute a program.
	OnRequestExec(requestID uint64, program string)
	// OnRequestPty creates an audit log message for a channel request to create an interactive terminal.
	OnRequestPty(requestID uint64, term string, columns uint32, rows uint32, width uint32, height uint32, modeList []byte)
	// OnRequestX11 create an audit log message for a channel request to start X11 forwarding
	OnRequestX11(requestID uint64, singleConnection bool, protocol string, cookie string, screen uint32)
	// OnRequestShell creates an audit log message for a channel request to execute a shell.
	OnRequestShell(requestID uint64)
	// OnRequestSignal creates an audit log message for a channel request to send a signal to the currently running
	//               program.
	OnRequestSignal(requestID uint64, signal string)
	// OnRequestSubsystem creates an audit log message for a channel request to execute a well-known subsystem (e.g. SFTP)
	OnRequestSubsystem(requestID uint64, subsystem string)
	// OnRequestWindow creates an audit log message for a channel request to resize the current window.
	OnRequestWindow(requestID uint64, columns uint32, rows uint32, width uint32, height uint32)

	// GetStdinProxy creates an intercepting audit log reader proxy for the standard input.
	GetStdinProxy(stdin io.Reader) io.Reader
	// GetStdoutProxy creates an intercepting audit log writer proxy for the standard output.
	GetStdoutProxy(stdout io.Writer) io.Writer
	// GetStderrProxy creates an intercepting audit log writer proxy for the standard error.
	GetStderrProxy(stderr io.Writer) io.Writer
	// GetForwardingProxy creates an intercepting audit log writer proxy for forwarding channels.
	GetForwardingProxy(forward io.ReadWriteCloser) io.ReadWriteCloser

	// OnExit is called when the executed program quits. The exitStatus parameter contains the exit code of the
	// application.
	OnExit(exitStatus uint32)

	// OnExitSignal is called when the executed program quits due to a signal.
	OnExitSignal(signal string, coreDumped bool, errorMessage string, languageTag string)

	// OnWriteClose is called when the channel is closed for writing.
	OnWriteClose()

	// OnClose is called when the channel is closed.
	OnClose()
}

Channel is an audit logger for one specific channel

type Connection

type Connection interface {
	// OnDisconnect creates an audit log message for a disconnect event.
	OnDisconnect()

	// OnAuthPassword creates an audit log message for an authentication attempt.
	OnAuthPassword(username string, password []byte)
	// OnAuthPasswordSuccess creates an audit log message for a successful authentication.
	OnAuthPasswordSuccess(username string, password []byte)
	// OnAuthPasswordFailed creates an audit log message for a failed authentication.
	OnAuthPasswordFailed(username string, password []byte)
	// OnAuthPasswordBackendError creates an audit log message for an auth server (backend) error during password
	//                            verification.
	OnAuthPasswordBackendError(username string, password []byte, reason string)

	// OnAuthPubKey creates an audit log message for an authentication attempt with public key.
	OnAuthPubKey(username string, pubKey string)
	// OnAuthPubKeySuccess creates an audit log message for a successful public key authentication.
	OnAuthPubKeySuccess(username string, pubKey string)
	// OnAuthPubKeyFailed creates an audit log message for a failed public key authentication.
	OnAuthPubKeyFailed(username string, pubKey string)
	// OnAuthPubKeyBackendError creates an audit log message for a failure while talking to the auth server (backend)
	//                          during public key authentication.
	OnAuthPubKeyBackendError(username string, pubKey string, reason string)

	// OnAuthKeyboardInteractiveChallenge is sent when a keyboard-interactive challenge is issued to the user.
	// Multiple of these challenges can be issued to a user, even after a previous password or pubkey authentication.
	OnAuthKeyboardInteractiveChallenge(
		username string,
		instruction string,
		questions []message.KeyboardInteractiveQuestion,
	)
	// OnAuthKeyboardInteractiveAnswer is recording an answer from a user to a keyboard-interactive authentication.
	OnAuthKeyboardInteractiveAnswer(
		username string,
		answers []message.KeyboardInteractiveAnswer,
	)
	// OnAuthKeyboardInteractiveFailed records a failure during the keyboard-interactive authentication.
	OnAuthKeyboardInteractiveFailed(username string)
	// OnAuthKeyboardInteractiveBackendError records a backend failure during the keyboard-interactive authentication.
	OnAuthKeyboardInteractiveBackendError(username string, reason string)

	// OnHandshakeFailed creates an entry that indicates a handshake failure.
	OnHandshakeFailed(reason string)
	// OnHandshakeSuccessful creates an entry that indicates a successful SSH handshake.
	OnHandshakeSuccessful(username string)

	// OnGlobalRequestUnknown creates an audit log message for a global request that is not supported.
	OnGlobalRequestUnknown(requestType string)

	// OnGlobalRequestDecodeFailed creates an audit log message for a global request that is supported but could not be decoded
	OnGlobalRequestDecodeFailed(requestID uint64, requestType string, payload []byte, reason error)

	// OnNewChannel creates an audit log message for a new channel request.
	OnNewChannel(channelID message.ChannelID, channelType string)
	// OnNewChannelFailed creates an audit log message for a failure in requesting a new channel.
	OnNewChannelFailed(channelID message.ChannelID, channelType string, reason string)
	// OnNewChannelSuccess creates an audit log message for successfully requesting a new channel and returns a
	//                     channel-specific audit logger.
	OnNewChannelSuccess(channelID message.ChannelID, channelType string) Channel

	// OnRequestTCPReverseForward creates an audit log message for requesting the server to listen
	// on a host and port for incoming connections.
	OnRequestTCPReverseForward(bindHost string, bindPort uint32)

	// OnRequestCancelTCPReverseForward creates an audit log message for requesting the server to stop listening on a host and port for incoming connections.
	OnRequestCancelTCPReverseForward(bindHost string, bindPort uint32)

	// OnTCPForwardChannel creates an audit log message for requesting to open a network forwarding channel (proxy).
	OnTCPForwardChannel(channelID message.ChannelID, hostToConnect string, portToConnect uint32, originatorHost string, originatorPort uint32)

	// OnReverseForwardChannel creates an audit log message for requesting to open a reverse forwarding channel after a connection is received on a listening port.
	OnReverseForwardChannel(channelID message.ChannelID, connectedHost string, connectedPort uint32, originatorHost string, originatorPort uint32)

	// OnReverseStreamLocalChannel creates an audit log message for requesting to open a reverse forwarding channel after a connection is received on a listening unix socket.
	OnReverseStreamLocalChannel(channelID message.ChannelID, path string)

	// OnReverseX11ForwardChannel creates an audit log message for requesting to open a channel to forward an X11 connection to the client.
	OnReverseX11ForwardChannel(channelID message.ChannelID, originatorHost string, originatorPort uint32)

	// OnDirectStreamLocal creates an audit log message for requesting to open a unix socket forwarding channel.
	OnDirectStreamLocal(channelID message.ChannelID, path string)

	// OnRequestStreamLocal creates an audit log message for requesting the server to listen on a unix socket for incoming connections.
	OnRequestStreamLocal(path string)

	// OnRequestCancelStreamLocal creates an audit log message for requesting the server to stop listening on a unix socket for incoming connections.
	OnRequestCancelStreamLocal(path string)
}

Connection is an audit logger for a specific connection

type Logger

type Logger interface {
	// OnConnect creates an audit log message for a new connection and simultaneously returns a connection object for
	//           connection-specific messages
	OnConnect(connectionID message.ConnectionID, ip net.TCPAddr) (Connection, error)
	// Shutdown triggers all failing uploads to cancel, waits for all currently running uploads to finish, then returns.
	// When the shutdownContext expires it will do its best to immediately upload any running background processes.
	Shutdown(shutdownContext context.Context)
}

Logger is a top level audit logger.

func New

func New(config config.AuditLogConfig, geoIPLookupProvider geoipprovider.LookupProvider, logger log.Logger) (Logger, error)

New Creates a new audit logging pipeline based on the provided configuration.

func NewLogger

func NewLogger(
	intercept config.AuditLogInterceptConfig,
	encoder codec.Encoder,
	storage storage.WritableStorage,
	logger log.Logger,
	geoIPLookup geoipprovider.LookupProvider,
) (Logger, error)

NewLogger creates a new audit logging pipeline with the provided elements.

Directories

Path Synopsis
generate
s3

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL