auditlog

package module
v0.9.5 Latest Latest
Warning

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

Go to latest
Published: Dec 11, 2020 License: MIT Imports: 17 Imported by: 3

README

ContainerSSH - Launch Containers on Demand

ContainerSSH Audit Logging Library

Go Report Card LGTM Alerts

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.

Note: This is a developer documentation.
The user documentation for ContainerSSH is located at containerssh.github.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/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.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewEncoder

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

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

func NewStorage

func NewStorage(config Config, 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)
	// OnRequestExec creates an audit log message for a channel request to execute a shell.
	OnRequestShell(requestID uint64)
	// OnRequestExec creates an audit log message for a channel request to send a signal to the currently running
	//               program.
	OnRequestSignal(requestID uint64, signal string)
	// OnRequestExec 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
	// GetStdinProxy creates an intercepting audit log writer proxy for the standard output.
	GetStdoutProxy(stdout io.Writer) io.Writer
	// GetStdinProxy creates an intercepting audit log writer proxy for the standard error.
	GetStderrProxy(stderr io.Writer) io.Writer

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

Channel is an audit logger for one specific hannel

type Config

type Config struct {
	// Enable turns on audit logging.
	Enable bool `json:"enable" yaml:"enable" default:"false"`
	// Format audit format
	Format Format `json:"format" yaml:"format" default:"none"`
	// Storage audit storage type
	Storage Storage `json:"storage" yaml:"storage" default:"none"`
	// File audit logger configuration
	File file.Config `json:"file" yaml:"file"`
	// S3 configuration
	S3 s3.Config `json:"s3" yaml:"s3"`
	// Intercept configures what should be intercepted
	Intercept InterceptConfig `json:"intercept" yaml:"intercept"`
}

Config is the configuration structure for audit logging.

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)

	// 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)

	// 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
}

Connection is an audit logger for a specific connection

type Format

type Format string

Format describes the audit log format in use.

const (
	// FormatNone signals that no audit logging should take place.
	FormatNone Format = "none"
	// FormatBinary signals that audit logging should take place in CBOR+GZIP format
	//              (see https://containerssh.github.io/advanced/audit/format/ )
	FormatBinary Format = "binary"
	// FormatAsciinema signals that audit logging should take place in Asciicast v2 format
	//                 (see https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md )
	FormatAsciinema Format = "asciinema"
)

type InterceptConfig

type InterceptConfig struct {
	// Stdin signals that the standard input from the user should be captured.
	Stdin bool `json:"stdin" yaml:"stdin" default:"false"`
	// Stdout signals that the standard output to the user should be captured.
	Stdout bool `json:"stdout" yaml:"stdout" default:"false"`
	// Stderr signals that the standard error to the user should be captured.
	Stderr bool `json:"stderr" yaml:"stderr" default:"false"`
	// Passwords signals that passwords during authentication should be captured.
	Passwords bool `json:"passwords" yaml:"passwords" default:"false"`
}

InterceptConfig configures what should be intercepted by the auditing facility.

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, 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 InterceptConfig,
	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.

type Storage

type Storage string

Storage describes the storage backend to use.

const (
	// StorageNone signals that no storage should be used.
	StorageNone Storage = "none"
	// StorageFile signals that audit logs should be stored in a local directory.
	StorageFile Storage = "file"
	// StorageS3 signals that audit logs should be stored in an S3-compatible object storage.
	StorageS3 Storage = "s3"
)

Directories

Path Synopsis
s3

Jump to

Keyboard shortcuts

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