Skip to content

Commit 97e62cd

Browse files
Mark Gritterswayne275
andauthored
Send a test message before committing a new audit device. (hashicorp#10520)
* Send a test message before committing a new audit device. Also, lower timeout on connection attempts in socket device. * added changelog * go mod vendor (picked up some unrelated changes.) * Skip audit device check in integration test. Co-authored-by: swayne275 <swayne@hashicorp.com>
1 parent 1f21ca5 commit 97e62cd

File tree

11 files changed

+174
-2
lines changed

11 files changed

+174
-2
lines changed

audit/audit.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ type Backend interface {
2424
// a possibility.
2525
LogResponse(context.Context, *logical.LogInput) error
2626

27+
// LogTestMessage is used to check an audit backend before adding it
28+
// permanently. It should attempt to synchronously log the given test
29+
// message, WITHOUT using the normal Salt (which would require a storage
30+
// operation on creation, which is currently disallowed.)
31+
LogTestMessage(context.Context, *logical.LogInput, map[string]string) error
32+
2733
// GetHash is used to return the given data with the backend's hash,
2834
// so that a caller can determine if a value in the audit log matches
2935
// an expected plaintext value

audit/format.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,3 +434,25 @@ func parseVaultTokenFromJWT(token string) *string {
434434

435435
return &claims.ID
436436
}
437+
438+
// Create a formatter not backed by a persistent salt.
439+
func NewTemporaryFormatter(format, prefix string) *AuditFormatter {
440+
temporarySalt := func(ctx context.Context) (*salt.Salt, error) {
441+
return salt.NewNonpersistentSalt(), nil
442+
}
443+
ret := &AuditFormatter{}
444+
445+
switch format {
446+
case "jsonx":
447+
ret.AuditFormatWriter = &JSONxFormatWriter{
448+
Prefix: prefix,
449+
SaltFunc: temporarySalt,
450+
}
451+
default:
452+
ret.AuditFormatWriter = &JSONFormatWriter{
453+
Prefix: prefix,
454+
SaltFunc: temporarySalt,
455+
}
456+
}
457+
return ret
458+
}

builtin/audit/file/backend.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,24 @@ func (b *Backend) LogResponse(ctx context.Context, in *logical.LogInput) error {
258258
return b.log(ctx, buf, writer)
259259
}
260260

261+
func (b *Backend) LogTestMessage(ctx context.Context, in *logical.LogInput, config map[string]string) error {
262+
var writer io.Writer
263+
switch b.path {
264+
case "stdout":
265+
writer = os.Stdout
266+
case "discard":
267+
return nil
268+
}
269+
270+
var buf bytes.Buffer
271+
temporaryFormatter := audit.NewTemporaryFormatter(config["format"], config["prefix"])
272+
if err := temporaryFormatter.FormatRequest(ctx, &buf, b.formatConfig, in); err != nil {
273+
return err
274+
}
275+
276+
return b.log(ctx, &buf, writer)
277+
}
278+
261279
// The file lock must be held before calling this
262280
func (b *Backend) open() error {
263281
if b.f != nil {

builtin/audit/socket/backend.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,30 @@ func (b *Backend) LogResponse(ctx context.Context, in *logical.LogInput) error {
177177
return err
178178
}
179179

180+
func (b *Backend) LogTestMessage(ctx context.Context, in *logical.LogInput, config map[string]string) error {
181+
var buf bytes.Buffer
182+
temporaryFormatter := audit.NewTemporaryFormatter(config["format"], config["prefix"])
183+
if err := temporaryFormatter.FormatRequest(ctx, &buf, b.formatConfig, in); err != nil {
184+
return err
185+
}
186+
187+
b.Lock()
188+
defer b.Unlock()
189+
190+
err := b.write(ctx, buf.Bytes())
191+
if err != nil {
192+
rErr := b.reconnect(ctx)
193+
if rErr != nil {
194+
err = multierror.Append(err, rErr)
195+
} else {
196+
// Try once more after reconnecting
197+
err = b.write(ctx, buf.Bytes())
198+
}
199+
}
200+
201+
return err
202+
}
203+
180204
func (b *Backend) write(ctx context.Context, buf []byte) error {
181205
if b.connection == nil {
182206
if err := b.reconnect(ctx); err != nil {
@@ -203,8 +227,11 @@ func (b *Backend) reconnect(ctx context.Context) error {
203227
b.connection = nil
204228
}
205229

230+
timeoutContext, cancel := context.WithTimeout(ctx, b.writeDuration)
231+
defer cancel()
232+
206233
dialer := net.Dialer{}
207-
conn, err := dialer.DialContext(ctx, b.socketType, b.address)
234+
conn, err := dialer.DialContext(timeoutContext, b.socketType, b.address)
208235
if err != nil {
209236
return err
210237
}

builtin/audit/syslog/backend.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,18 @@ func (b *Backend) LogResponse(ctx context.Context, in *logical.LogInput) error {
140140
return err
141141
}
142142

143+
func (b *Backend) LogTestMessage(ctx context.Context, in *logical.LogInput, config map[string]string) error {
144+
var buf bytes.Buffer
145+
temporaryFormatter := audit.NewTemporaryFormatter(config["format"], config["prefix"])
146+
if err := temporaryFormatter.FormatRequest(ctx, &buf, b.formatConfig, in); err != nil {
147+
return err
148+
}
149+
150+
// Send to syslog
151+
_, err := b.logger.Write(buf.Bytes())
152+
return err
153+
}
154+
143155
func (b *Backend) Reload(_ context.Context) error {
144156
return nil
145157
}

changelog/10520.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:improvement
2+
core: Check audit device with a test message before adding it.
3+
```

command/audit_enable_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ func TestAuditEnableCommand_Run(t *testing.T) {
189189
case "file":
190190
args = append(args, "file_path=discard")
191191
case "socket":
192-
args = append(args, "address=127.0.0.1:8888")
192+
args = append(args, "address=127.0.0.1:8888",
193+
"skip_test=true")
193194
case "syslog":
194195
if _, exists := os.LookupEnv("WSLENV"); exists {
195196
t.Log("skipping syslog test on WSL")

sdk/helper/salt/salt.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,23 @@ func NewSalt(ctx context.Context, view logical.Storage, config *Config) (*Salt,
115115
return s, nil
116116
}
117117

118+
// NewNonpersistentSalt creates a new salt with default configuration and no storage usage.
119+
func NewNonpersistentSalt() *Salt {
120+
// Setup the configuration
121+
config := &Config{}
122+
config.Location = ""
123+
config.HashFunc = SHA256Hash
124+
config.HMAC = sha256.New
125+
config.HMACType = "hmac-sha256"
126+
127+
s := &Salt{
128+
config: config,
129+
}
130+
s.salt, _ = uuid.GenerateUUID()
131+
s.generated = true
132+
return s
133+
}
134+
118135
// SaltID is used to apply a salt and hash function to an ID to make sure
119136
// it is not reversible
120137
func (s *Salt) SaltID(id string) string {

vault/audit.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ var (
4040
errLoadAuditFailed = errors.New("failed to setup audit table")
4141
)
4242

43+
func (c *Core) generateAuditTestProbe() (*logical.LogInput, error) {
44+
requestId, err := uuid.GenerateUUID()
45+
if err != nil {
46+
return nil, err
47+
}
48+
return &logical.LogInput{
49+
Type: "request",
50+
Auth: nil,
51+
Request: &logical.Request{
52+
ID: requestId,
53+
Operation: "update",
54+
Path: "sys/audit/test",
55+
},
56+
Response: nil,
57+
OuterErr: nil,
58+
}, nil
59+
}
60+
4361
// enableAudit is used to enable a new audit backend
4462
func (c *Core) enableAudit(ctx context.Context, entry *MountEntry, updateStorage bool) error {
4563
// Ensure we end the path in a slash
@@ -103,6 +121,20 @@ func (c *Core) enableAudit(ctx context.Context, entry *MountEntry, updateStorage
103121
return fmt.Errorf("nil audit backend of type %q returned from factory", entry.Type)
104122
}
105123

124+
if entry.Options["skip_test"] != "true" {
125+
// Test the new audit device and report failure if it doesn't work.
126+
testProbe, err := c.generateAuditTestProbe()
127+
if err != nil {
128+
return err
129+
}
130+
err = backend.LogTestMessage(ctx, testProbe, entry.Options)
131+
if err != nil {
132+
c.logger.Error("new audit backend failed test", "path", entry.Path, "type", entry.Type, "error", err)
133+
return fmt.Errorf("audit backend failed test message: %w", err)
134+
135+
}
136+
}
137+
106138
newTable := c.audit.shallowClone()
107139
newTable.Entries = append(newTable.Entries, entry)
108140

vault/testing.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,19 @@ func (n *noopAudit) LogResponse(ctx context.Context, in *logical.LogInput) error
561561
return nil
562562
}
563563

564+
func (n *noopAudit) LogTestMessage(ctx context.Context, in *logical.LogInput, config map[string]string) error {
565+
n.l.Lock()
566+
defer n.l.Unlock()
567+
var w bytes.Buffer
568+
tempFormatter := audit.NewTemporaryFormatter(config["format"], config["prefix"])
569+
err := tempFormatter.FormatResponse(ctx, &w, audit.FormatterConfig{}, in)
570+
if err != nil {
571+
return err
572+
}
573+
n.records = append(n.records, w.Bytes())
574+
return nil
575+
}
576+
564577
func (n *noopAudit) Reload(_ context.Context) error {
565578
return nil
566579
}
@@ -2127,6 +2140,10 @@ func (n *NoopAudit) LogResponse(ctx context.Context, in *logical.LogInput) error
21272140
return n.RespErr
21282141
}
21292142

2143+
func (n *NoopAudit) LogTestMessage(ctx context.Context, in *logical.LogInput, options map[string]string) error {
2144+
return nil
2145+
}
2146+
21302147
func (n *NoopAudit) Salt(ctx context.Context) (*salt.Salt, error) {
21312148
n.saltMutex.RLock()
21322149
if n.salt != nil {

0 commit comments

Comments
 (0)