Skip to content

Commit

Permalink
Add audit helper with context to allow for control from calling appli…
Browse files Browse the repository at this point in the history
…cation (#91)

* add audit helper with context to allow for control from calling application

* Signed commit
  • Loading branch information
fishnix authored Aug 18, 2022
1 parent d628aa0 commit 0cff720
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 10 deletions.
68 changes: 58 additions & 10 deletions helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ limitations under the License.
package helpers

import (
"context"
"errors"
"fmt"
"os"
"time"
Expand All @@ -35,16 +37,9 @@ const (
// It assumes that audit events are less than 4096 bytes to ensure atomicity.
// it takes a writer for the audit log.
func OpenAuditLogFileUntilSuccess(path string, loggers ...logr.Logger) (*os.File, error) {
var l logr.Logger

if len(loggers) > 0 {
l = loggers[0]
} else {
z, err := zap.NewProduction()
if err != nil {
return nil, fmt.Errorf("failed to create zap logger: %w", err)
}
l = zapr.NewLogger(z)
l, err := newLogger(loggers...)
if err != nil {
return nil, err
}

l.Info("opening audit log file. This will block until the file is available", "path", path)
Expand All @@ -68,3 +63,56 @@ func OpenAuditLogFileUntilSuccess(path string, loggers ...logr.Logger) (*os.File
return fd, nil
}
}

// OpenAuditLogFileUntilSuccessWithContext attempts to open a file for writing audit events until
// it succeeds or the context is cancelled.
// It assumes that audit events are less than 4096 bytes to ensure atomicity.
// it takes a writer for the audit log.
func OpenAuditLogFileUntilSuccessWithContext(ctx context.Context, path string, ls ...logr.Logger) (*os.File, error) {
l, err := newLogger(ls...)
if err != nil {
return nil, err
}

l.Info("opening audit log file. This will block until the file is available or context is cancelled", "path", path)

ticker := time.NewTicker(retryInterval)
defer ticker.Stop()

for ; true; <-ticker.C {
if err := ctx.Err(); err != nil {
return nil, err
}

// This is opened with the O_APPEND option to ensure
// atomicity of writes. This is important to ensure
// we can concurrently write to the file and not block
// the server's main loop.
fd, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, ownerGroupAccess)
if err != nil {
if os.IsNotExist(err) {
continue
}
// Not being able to write audit log events is a fatal error
return nil, err
}

l.Info("audit log file opened successfully", "path", path)
return fd, nil
}

return nil, errors.New("unexpected audit log error")
}

func newLogger(loggers ...logr.Logger) (logr.Logger, error) {
if len(loggers) > 0 {
return loggers[0], nil
}

z, err := zap.NewProduction()
if err != nil {
return logr.Logger{}, fmt.Errorf("failed to create zap logger: %w", err)
}

return zapr.NewLogger(z), nil
}
78 changes: 78 additions & 0 deletions helpers/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
package helpers_test

import (
"context"
"os"
"path/filepath"
"sync"
Expand Down Expand Up @@ -63,6 +64,83 @@ func TestOpenAuditLogFileUntilSuccess(t *testing.T) {
require.NoError(t, err)
}

func TestOpenAuditLogFileUntilSuccessWithContext(t *testing.T) {
t.Parallel()

var wg sync.WaitGroup
wg.Add(1)

tmpdir := t.TempDir()
tmpfile := filepath.Join(tmpdir, "audit.log")

go func() {
defer wg.Done()
time.Sleep(time.Second)
fd, err := os.OpenFile(tmpfile, os.O_RDONLY|os.O_CREATE, 0o600)
require.NoError(t, err)
err = fd.Close()
require.NoError(t, err)
}()

fd, err := helpers.OpenAuditLogFileUntilSuccessWithContext(context.TODO(), tmpfile)
require.NoError(t, err)
require.NotNil(t, fd)

err = fd.Close()
require.NoError(t, err)

// We wait so we don't leak file descriptors
wg.Wait()

err = os.Remove(tmpfile)
require.NoError(t, err)
}

func TestOpenAuditLogFileUntilSuccessWithContextClosed(t *testing.T) {
t.Parallel()

ctx, cancel := context.WithCancel(context.Background())

go func(c context.CancelFunc) {
time.Sleep(time.Second)
c()
}(cancel)

fd, err := helpers.OpenAuditLogFileUntilSuccessWithContext(ctx, "/noexist")
require.ErrorIs(t, err, context.Canceled)
require.Nil(t, fd)
}

func TestOpenAuditLogFileUntilSuccessWithContextError(t *testing.T) {
t.Parallel()

var wg sync.WaitGroup
wg.Add(1)

tmpdir := t.TempDir()
tmpfile := filepath.Join(tmpdir, "audit.log")

go func() {
defer wg.Done()
time.Sleep(time.Second)
// This file is read only
fd, err := os.OpenFile(tmpfile, os.O_RDONLY|os.O_CREATE, 0o500)
require.NoError(t, err)
err = fd.Close()
require.NoError(t, err)
}()

fd, err := helpers.OpenAuditLogFileUntilSuccessWithContext(context.TODO(), tmpfile)
require.Error(t, err)
require.Nil(t, fd)

// We wait so we don't leak file descriptors
wg.Wait()

err = os.Remove(tmpfile)
require.NoError(t, err)
}

func TestOpenAuditLogFileError(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 0cff720

Please sign in to comment.