Send a test message before committing a new audit device. (#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>
This commit is contained in:
parent
5ac1c93c4a
commit
8c67bed7ae
|
@ -24,6 +24,12 @@ type Backend interface {
|
||||||
// a possibility.
|
// a possibility.
|
||||||
LogResponse(context.Context, *logical.LogInput) error
|
LogResponse(context.Context, *logical.LogInput) error
|
||||||
|
|
||||||
|
// LogTestMessage is used to check an audit backend before adding it
|
||||||
|
// permanently. It should attempt to synchronously log the given test
|
||||||
|
// message, WITHOUT using the normal Salt (which would require a storage
|
||||||
|
// operation on creation, which is currently disallowed.)
|
||||||
|
LogTestMessage(context.Context, *logical.LogInput, map[string]string) error
|
||||||
|
|
||||||
// GetHash is used to return the given data with the backend's hash,
|
// GetHash is used to return the given data with the backend's hash,
|
||||||
// so that a caller can determine if a value in the audit log matches
|
// so that a caller can determine if a value in the audit log matches
|
||||||
// an expected plaintext value
|
// an expected plaintext value
|
||||||
|
|
|
@ -434,3 +434,25 @@ func parseVaultTokenFromJWT(token string) *string {
|
||||||
|
|
||||||
return &claims.ID
|
return &claims.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a formatter not backed by a persistent salt.
|
||||||
|
func NewTemporaryFormatter(format, prefix string) *AuditFormatter {
|
||||||
|
temporarySalt := func(ctx context.Context) (*salt.Salt, error) {
|
||||||
|
return salt.NewNonpersistentSalt(), nil
|
||||||
|
}
|
||||||
|
ret := &AuditFormatter{}
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case "jsonx":
|
||||||
|
ret.AuditFormatWriter = &JSONxFormatWriter{
|
||||||
|
Prefix: prefix,
|
||||||
|
SaltFunc: temporarySalt,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ret.AuditFormatWriter = &JSONFormatWriter{
|
||||||
|
Prefix: prefix,
|
||||||
|
SaltFunc: temporarySalt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
|
@ -258,6 +258,24 @@ func (b *Backend) LogResponse(ctx context.Context, in *logical.LogInput) error {
|
||||||
return b.log(ctx, buf, writer)
|
return b.log(ctx, buf, writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Backend) LogTestMessage(ctx context.Context, in *logical.LogInput, config map[string]string) error {
|
||||||
|
var writer io.Writer
|
||||||
|
switch b.path {
|
||||||
|
case "stdout":
|
||||||
|
writer = os.Stdout
|
||||||
|
case "discard":
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
temporaryFormatter := audit.NewTemporaryFormatter(config["format"], config["prefix"])
|
||||||
|
if err := temporaryFormatter.FormatRequest(ctx, &buf, b.formatConfig, in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.log(ctx, &buf, writer)
|
||||||
|
}
|
||||||
|
|
||||||
// The file lock must be held before calling this
|
// The file lock must be held before calling this
|
||||||
func (b *Backend) open() error {
|
func (b *Backend) open() error {
|
||||||
if b.f != nil {
|
if b.f != nil {
|
||||||
|
|
|
@ -177,6 +177,30 @@ func (b *Backend) LogResponse(ctx context.Context, in *logical.LogInput) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Backend) LogTestMessage(ctx context.Context, in *logical.LogInput, config map[string]string) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
temporaryFormatter := audit.NewTemporaryFormatter(config["format"], config["prefix"])
|
||||||
|
if err := temporaryFormatter.FormatRequest(ctx, &buf, b.formatConfig, in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
err := b.write(ctx, buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
rErr := b.reconnect(ctx)
|
||||||
|
if rErr != nil {
|
||||||
|
err = multierror.Append(err, rErr)
|
||||||
|
} else {
|
||||||
|
// Try once more after reconnecting
|
||||||
|
err = b.write(ctx, buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Backend) write(ctx context.Context, buf []byte) error {
|
func (b *Backend) write(ctx context.Context, buf []byte) error {
|
||||||
if b.connection == nil {
|
if b.connection == nil {
|
||||||
if err := b.reconnect(ctx); err != nil {
|
if err := b.reconnect(ctx); err != nil {
|
||||||
|
@ -203,8 +227,11 @@ func (b *Backend) reconnect(ctx context.Context) error {
|
||||||
b.connection = nil
|
b.connection = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timeoutContext, cancel := context.WithTimeout(ctx, b.writeDuration)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
dialer := net.Dialer{}
|
dialer := net.Dialer{}
|
||||||
conn, err := dialer.DialContext(ctx, b.socketType, b.address)
|
conn, err := dialer.DialContext(timeoutContext, b.socketType, b.address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,6 +140,18 @@ func (b *Backend) LogResponse(ctx context.Context, in *logical.LogInput) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Backend) LogTestMessage(ctx context.Context, in *logical.LogInput, config map[string]string) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
temporaryFormatter := audit.NewTemporaryFormatter(config["format"], config["prefix"])
|
||||||
|
if err := temporaryFormatter.FormatRequest(ctx, &buf, b.formatConfig, in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send to syslog
|
||||||
|
_, err := b.logger.Write(buf.Bytes())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Backend) Reload(_ context.Context) error {
|
func (b *Backend) Reload(_ context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
core: Check audit device with a test message before adding it.
|
||||||
|
```
|
|
@ -189,7 +189,8 @@ func TestAuditEnableCommand_Run(t *testing.T) {
|
||||||
case "file":
|
case "file":
|
||||||
args = append(args, "file_path=discard")
|
args = append(args, "file_path=discard")
|
||||||
case "socket":
|
case "socket":
|
||||||
args = append(args, "address=127.0.0.1:8888")
|
args = append(args, "address=127.0.0.1:8888",
|
||||||
|
"skip_test=true")
|
||||||
case "syslog":
|
case "syslog":
|
||||||
if _, exists := os.LookupEnv("WSLENV"); exists {
|
if _, exists := os.LookupEnv("WSLENV"); exists {
|
||||||
t.Log("skipping syslog test on WSL")
|
t.Log("skipping syslog test on WSL")
|
||||||
|
|
|
@ -115,6 +115,23 @@ func NewSalt(ctx context.Context, view logical.Storage, config *Config) (*Salt,
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewNonpersistentSalt creates a new salt with default configuration and no storage usage.
|
||||||
|
func NewNonpersistentSalt() *Salt {
|
||||||
|
// Setup the configuration
|
||||||
|
config := &Config{}
|
||||||
|
config.Location = ""
|
||||||
|
config.HashFunc = SHA256Hash
|
||||||
|
config.HMAC = sha256.New
|
||||||
|
config.HMACType = "hmac-sha256"
|
||||||
|
|
||||||
|
s := &Salt{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
s.salt, _ = uuid.GenerateUUID()
|
||||||
|
s.generated = true
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// SaltID is used to apply a salt and hash function to an ID to make sure
|
// SaltID is used to apply a salt and hash function to an ID to make sure
|
||||||
// it is not reversible
|
// it is not reversible
|
||||||
func (s *Salt) SaltID(id string) string {
|
func (s *Salt) SaltID(id string) string {
|
||||||
|
|
|
@ -40,6 +40,24 @@ var (
|
||||||
errLoadAuditFailed = errors.New("failed to setup audit table")
|
errLoadAuditFailed = errors.New("failed to setup audit table")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *Core) generateAuditTestProbe() (*logical.LogInput, error) {
|
||||||
|
requestId, err := uuid.GenerateUUID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &logical.LogInput{
|
||||||
|
Type: "request",
|
||||||
|
Auth: nil,
|
||||||
|
Request: &logical.Request{
|
||||||
|
ID: requestId,
|
||||||
|
Operation: "update",
|
||||||
|
Path: "sys/audit/test",
|
||||||
|
},
|
||||||
|
Response: nil,
|
||||||
|
OuterErr: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// enableAudit is used to enable a new audit backend
|
// enableAudit is used to enable a new audit backend
|
||||||
func (c *Core) enableAudit(ctx context.Context, entry *MountEntry, updateStorage bool) error {
|
func (c *Core) enableAudit(ctx context.Context, entry *MountEntry, updateStorage bool) error {
|
||||||
// Ensure we end the path in a slash
|
// Ensure we end the path in a slash
|
||||||
|
@ -103,6 +121,20 @@ func (c *Core) enableAudit(ctx context.Context, entry *MountEntry, updateStorage
|
||||||
return fmt.Errorf("nil audit backend of type %q returned from factory", entry.Type)
|
return fmt.Errorf("nil audit backend of type %q returned from factory", entry.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if entry.Options["skip_test"] != "true" {
|
||||||
|
// Test the new audit device and report failure if it doesn't work.
|
||||||
|
testProbe, err := c.generateAuditTestProbe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = backend.LogTestMessage(ctx, testProbe, entry.Options)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("new audit backend failed test", "path", entry.Path, "type", entry.Type, "error", err)
|
||||||
|
return fmt.Errorf("audit backend failed test message: %w", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newTable := c.audit.shallowClone()
|
newTable := c.audit.shallowClone()
|
||||||
newTable.Entries = append(newTable.Entries, entry)
|
newTable.Entries = append(newTable.Entries, entry)
|
||||||
|
|
||||||
|
|
|
@ -561,6 +561,19 @@ func (n *noopAudit) LogResponse(ctx context.Context, in *logical.LogInput) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *noopAudit) LogTestMessage(ctx context.Context, in *logical.LogInput, config map[string]string) error {
|
||||||
|
n.l.Lock()
|
||||||
|
defer n.l.Unlock()
|
||||||
|
var w bytes.Buffer
|
||||||
|
tempFormatter := audit.NewTemporaryFormatter(config["format"], config["prefix"])
|
||||||
|
err := tempFormatter.FormatResponse(ctx, &w, audit.FormatterConfig{}, in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.records = append(n.records, w.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (n *noopAudit) Reload(_ context.Context) error {
|
func (n *noopAudit) Reload(_ context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2127,6 +2140,10 @@ func (n *NoopAudit) LogResponse(ctx context.Context, in *logical.LogInput) error
|
||||||
return n.RespErr
|
return n.RespErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NoopAudit) LogTestMessage(ctx context.Context, in *logical.LogInput, options map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (n *NoopAudit) Salt(ctx context.Context) (*salt.Salt, error) {
|
func (n *NoopAudit) Salt(ctx context.Context) (*salt.Salt, error) {
|
||||||
n.saltMutex.RLock()
|
n.saltMutex.RLock()
|
||||||
if n.salt != nil {
|
if n.salt != nil {
|
||||||
|
|
|
@ -115,6 +115,23 @@ func NewSalt(ctx context.Context, view logical.Storage, config *Config) (*Salt,
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewNonpersistentSalt creates a new salt with default configuration and no storage usage.
|
||||||
|
func NewNonpersistentSalt() *Salt {
|
||||||
|
// Setup the configuration
|
||||||
|
config := &Config{}
|
||||||
|
config.Location = ""
|
||||||
|
config.HashFunc = SHA256Hash
|
||||||
|
config.HMAC = sha256.New
|
||||||
|
config.HMACType = "hmac-sha256"
|
||||||
|
|
||||||
|
s := &Salt{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
s.salt, _ = uuid.GenerateUUID()
|
||||||
|
s.generated = true
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// SaltID is used to apply a salt and hash function to an ID to make sure
|
// SaltID is used to apply a salt and hash function to an ID to make sure
|
||||||
// it is not reversible
|
// it is not reversible
|
||||||
func (s *Salt) SaltID(id string) string {
|
func (s *Salt) SaltID(id string) string {
|
||||||
|
|
Loading…
Reference in New Issue