Skip to content

Commit

Permalink
Move OpenAuditLogFileUntilSuccess to new helpers package (#31)
Browse files Browse the repository at this point in the history
This function is relevant also outside of the `ginaudit` package, so
let's make it available through a new package.

Signed-off-by: Juan Antonio Osorio <[email protected]>
  • Loading branch information
JAORMX authored Apr 25, 2022
1 parent 800c2c2 commit 96f6bf7
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 97 deletions.
4 changes: 2 additions & 2 deletions docs/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ It is often the case that one must not start to process events until the audit l
capabilities are set up. For this, the following pattern is suggested:

```golang
fd, err := ginaudit.OpenAuditLogFileUntilSuccess(auditLogPath)
fd, err := helpers.OpenAuditLogFileUntilSuccess(auditLogPath)
if err != nil {
panic(err)
}
Expand All @@ -36,7 +36,7 @@ defer fd.Close()
mdw := ginaudit.NewJSONMiddleware("my-test-component", fd)
```

The function `ginaudit.OpenAuditLogFileUntilSuccess` attempts to open the audit log
The function `helpers.OpenAuditLogFileUntilSuccess` attempts to open the audit log
file, and will block until it's available. This file may be created beforehand or it
may be created by another process e.g. a sidecar container. It opens the file with
`O_APPEND` which enables atomic writes as long as the audit events are less than 4096 bytes.
Expand Down
30 changes: 0 additions & 30 deletions ginaudit/mdw.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ import (
"fmt"
"io"
"net/http"
"os"
"sync"
"time"

"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
Expand All @@ -35,34 +33,6 @@ type Middleware struct {
eventTypeMap sync.Map
}

const (
ownerGroupAccess = 0o640
retryInterval = 100 * time.Millisecond
)

// OpenAuditLogFileUntilSuccess attempts to open a file for writing audit events until
// it succeeds.
// 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) (*os.File, error) {
for {
// 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) {
time.Sleep(retryInterval)
continue
}
// Not being able to write audit log events is a fatal error
return nil, err
}
return fd, nil
}
}

// NewMiddleware returns a new instance of audit Middleware.
func NewMiddleware(component string, aew *auditevent.EventWriter) *Middleware {
return &Middleware{
Expand Down
63 changes: 0 additions & 63 deletions ginaudit/mdw_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
Expand Down Expand Up @@ -319,68 +318,6 @@ func TestParallelCallsToMiddleware(t *testing.T) {
}
}

func TestOpenAuditLogFileUntilSuccess(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 := ginaudit.OpenAuditLogFileUntilSuccess(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 TestOpenAuditLogFileError(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 := ginaudit.OpenAuditLogFileUntilSuccess(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 TestCantRegisterMultipleTimesToSamePrometheus(t *testing.T) {
t.Parallel()

Expand Down
49 changes: 49 additions & 0 deletions helpers/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Copyright 2022 Equinix, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helpers

import (
"os"
"time"
)

const (
ownerGroupAccess = 0o640
retryInterval = 100 * time.Millisecond
)

// OpenAuditLogFileUntilSuccess attempts to open a file for writing audit events until
// it succeeds.
// 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) (*os.File, error) {
for {
// 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) {
time.Sleep(retryInterval)
continue
}
// Not being able to write audit log events is a fatal error
return nil, err
}
return fd, nil
}
}
90 changes: 90 additions & 0 deletions helpers/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
Copyright 2022 Equinix, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helpers_test

import (
"os"
"path/filepath"
"sync"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/metal-toolbox/auditevent/helpers"
)

func TestOpenAuditLogFileUntilSuccess(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.OpenAuditLogFileUntilSuccess(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 TestOpenAuditLogFileError(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.OpenAuditLogFileUntilSuccess(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)
}
4 changes: 2 additions & 2 deletions internal/testtools/testtools.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (

"github.com/stretchr/testify/require"

"github.com/metal-toolbox/auditevent/ginaudit"
"github.com/metal-toolbox/auditevent/helpers"
)

const (
Expand Down Expand Up @@ -63,7 +63,7 @@ func SetPipeReader(t *testing.T, namedPipe string) <-chan io.WriteCloser {
t.Helper()
rchan := make(chan io.WriteCloser)
go func(c chan<- io.WriteCloser) {
fd, err := ginaudit.OpenAuditLogFileUntilSuccess(namedPipe)
fd, err := helpers.OpenAuditLogFileUntilSuccess(namedPipe)
require.NoError(t, err)
c <- fd
}(rchan)
Expand Down

0 comments on commit 96f6bf7

Please sign in to comment.